From 12752bc0f9441ef9f929ee652eeb5d92b9dda4a5 Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Tue, 1 Sep 2026 14:41:33 +0000 Subject: pool: refuse a miner the select() loop cannot represent The operator event loop still builds an fd_set by hand (#26), and FD_SET on a descriptor the set cannot represent is undefined -- it writes past the bitmap. MAX_POOL_MINERS caps the miner list at 1000, but that is a count, and a count does not bound descriptor values: a Miner is removed only on clean disconnect, so half-open sessions hold their fds while the OS keeps issuing higher ones. Refuse the connection instead, and refuse on the limit that actually applies. On POSIX that is the descriptor value against FD_SETSIZE. On Windows an fd_set is a counted array of SOCKET handles whose values are arbitrary -- often large from the first accept -- so comparing a handle against FD_SETSIZE there would reject nearly every miner; the bound is how many sockets are held. This is a behaviour change: a pool at the limit now turns miners away with a log line rather than corrupting memory quietly. The real fix is poll/epoll, which #26 tracks. --- src/rpc.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'src/rpc.cpp') diff --git a/src/rpc.cpp b/src/rpc.cpp index 4ff09b9..a4b53ca 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -4697,6 +4697,31 @@ static void PoolEventLoop() sock_close(fd); continue; } + // #26: select() + FD_SET goes undefined once we exceed what an + // fd_set can carry, and MAX_POOL_MINERS (1000) alone does not + // prevent it -- a Miner is only removed on clean disconnect, so + // half-open zombies accumulate and the OS can hand out fds past + // the limit long before the count reaches 1000. Refuse rather + // than corrupt the fd_set. Which limit applies is platform + // specific: POSIX bounds the descriptor value, Windows the + // number of SOCKET handles an fd_set can hold. +#ifndef _WIN32 + if ((int)fd >= (int)FD_SETSIZE) { + LogPrint("worker", "[pool] refusing miner fd=%d -- at/over" + " FD_SETSIZE=%d, select() would be undefined (#26)\n", + (int)fd, (int)FD_SETSIZE); + sock_close(fd); + continue; + } +#else + if (gMiners.size() >= (size_t)FD_SETSIZE) { + LogPrint("worker", "[pool] refusing miner -- %zu sockets held," + " an fd_set carries at most FD_SETSIZE=%d (#26)\n", + gMiners.size(), (int)FD_SETSIZE); + sock_close(fd); + continue; + } +#endif gMiners.push_back(new Miner(fd)); LogPrint("worker", "[pool] miner fd=%d entered event loop" " -- awaiting subscribe/authorize\n", (int)fd); -- cgit v1.2.3