StreamSec Home  Forum Home 
 
Welcome, Guest.
Your IP: 216.73.216.150
2026-09-26 00:23:13 
 Announcements
 StreamSec Tools 4.1.1.354 - 4.1.3.357 - manual fix P02 On Linux, macOS, iOS and Android a socket wait writes past the end of its descriptor set, for a descriptor of 1024 or more.
Bottom
 
Total posts: 1
 Author StreamSec Tools 4.1.1.354 - 4.1.3.357 - manual fix P02 On Linux, macOS, iOS and Android a socket wait writes past the end of its descriptor set, for a descriptor of 1024 or more.
Henrick Wibell Hellström

2026-09-25 23:23:03
Registered user
WHAT GOES WRONG
tPosixSocket.WaitForData, and from 4.1.2.355 also WaitForWrite - the waits
behind every stSocket connection on Linux, macOS, iOS and Android - wait with
select(). A select() descriptor set has room for descriptors 0 to 1023 only,
and adding a higher one writes past the end of the set, a variable on the
stack; nothing reports it. Each socket holds three descriptors (the socket
itself and the pipe that Send uses to end a wait), so a process whose
open-file limit has been raised above 1024 reaches that range after a few
hundred sockets. From there a wait returns at once instead of waiting, or
the process fails with an access violation, in the wait or later, wherever
the overwritten memory is next used.

How many sockets a server has open is up to its clients: anyone who can
connect to it can push its new sockets past descriptor 1024. A process left
at the usual limit of 1024 open files never reaches that range. Windows is
not affected: its socket layer is separate, and its descriptor sets are
bounded.

WHAT THE FIX DOES
The waits use poll(), which has no such limit, instead of select(). Below
descriptor 1024 they behave exactly as before: the same timeouts, the same
readiness for data, for a peer that has closed and for a writable socket,
and a Send (and from 4.1.2.355 a Wake) still ends a wait in the same way.

WHO NEEDS IT
Applications built with these releases that run on Linux, macOS, iOS or
Android, connect through stSocket - which the TLS, HTTP, snip, SSH, mail and
OCSP components do on those platforms - and run with an open-file limit
above 1024. Servers matter most.

APPLIES TO
StreamSec Tools 4.1.1.354, 4.1.2.355, 4.1.3.356 and 4.1.3.357, in
stSocket.Posix.pas, which first shipped in 4.1.1.354. The functions the fix
replaces are the same in 4.1.2.355, 4.1.3.356 and 4.1.3.357. 4.1.1.354 has
only WaitForData, in a simpler form, and has its own replacement (PART 2B).
4.1.3.358 and later carry the fix.

HOW TO APPLY - in stSocket.Posix.pas
1. In the implementation section, directly before the line
       procedure SetNonBlocking(aFd: Integer);
   insert the declarations of PART 1.
2. Replace the function tPosixSocket.WaitForData, from "function" to its
   final "end;", with PART 2A - or, in 4.1.1.354, with PART 2B.
3. In 4.1.2.355, 4.1.3.356 and 4.1.3.357 only: replace the function
   tPosixSocket.WaitForWrite in the same way with PART 3.
Leave the uses clause as it is. Then rebuild the applications for Linux,
macOS, iOS and Android. The fix is pure Pascal; no assembler is involved.

-- PART 1 ----------------------------------------------------------------------
{ The waits use poll(), not select(): a select() descriptor set has room for
  descriptors 0 to 1023 only, and setting a higher one writes past its end.
  The RTL declares no poll, so it is declared here. }
const
  POLLIN   = $0001;
  POLLOUT  = $0004;
  POLLERR  = $0008;
  POLLHUP  = $0010;
  POLLNVAL = $0020;

  { Readiness as select() counts it: a peer that has hung up, or a pending
    socket error, still reads as ready, and the next Receive or Send reports
    it. }
  cReadReady  = POLLIN or POLLHUP or POLLERR;
  cWriteReady = POLLOUT or POLLERR;

type
  pollfd = record
    fd: Integer;
    events: Int16;
    revents: Int16;
  end;
  Ppollfd = ^pollfd;

function poll(fds: Ppollfd; nfds: NativeUInt; timeout: Integer): Integer; cdecl;
  external libc name _PU + 'poll';

{ poll() reads a negative timeout as "wait for ever"; a Cardinal above MaxInt
  would wrap into one. }
function PollTimeout(aMSec: Cardinal): Integer;
begin
  if aMSec > Cardinal(MaxInt) then
    Result := MaxInt
  else
    Result := Integer(aMSec);
end;

-- PART 2A: WaitForData, 4.1.2.355 - 4.1.3.357 -------------------------------
function tPosixSocket.WaitForData(aMSec: Cardinal): Boolean;
var
  lFds: array[0..1] of pollfd;
  lRes: Integer;
  lJunk: array[0..63] of Byte;
begin
  Result := False;
  if fSocket = cInvalidSocket then Exit;
  // A wake byte left by a Send while nobody waited must not end this wait.
  while __read(fWakeRd, @lJunk, SizeOf(lJunk)) > 0 do ;
  // An explicit Wake must: it is sticky.
  if TakePendingWake then Exit;
  lFds[0].fd := fSocket;
  lFds[0].events := POLLIN;
  lFds[0].revents := 0;
  lFds[1].fd := fWakeRd;
  lFds[1].events := POLLIN;
  lFds[1].revents := 0;
  lRes := poll(@lFds[0], 2, PollTimeout(aMSec));
  if lRes <= 0 then Exit;                     // 0 = timeout, <0 = error/EINTR
  // A descriptor that is not open is not readiness.
  if ((lFds[0].revents or lFds[1].revents) and POLLNVAL) <> 0 then Exit;
  if (lFds[1].revents and cReadReady) <> 0 then begin  // a Send or a Wake
    while __read(fWakeRd, @lJunk, SizeOf(lJunk)) > 0 do ;
    TakePendingWake;                          // delivered: do not keep it
    Lock; Unlock;                             // synchronise with the writer
    Exit;                                     // report "no read data"
  end;
  Result := (lFds[0].revents and cReadReady) <> 0;
end;

-- PART 2B: WaitForData, 4.1.1.354 --------------------------------------------
function tPosixSocket.WaitForData(aMSec: Cardinal): Boolean;
var
  lFds: array[0..1] of pollfd;
  lRes: Integer;
  lJunk: array[0..63] of Byte;
begin
  Result := False;
  if fSocket = cInvalidSocket then Exit;
  // A wake byte left by a Send while nobody waited must not end this wait.
  while __read(fWakeRd, @lJunk, SizeOf(lJunk)) > 0 do ;
  lFds[0].fd := fSocket;
  lFds[0].events := POLLIN;
  lFds[0].revents := 0;
  lFds[1].fd := fWakeRd;
  lFds[1].events := POLLIN;
  lFds[1].revents := 0;
  lRes := poll(@lFds[0], 2, PollTimeout(aMSec));
  if lRes <= 0 then Exit;                     // 0 = timeout, <0 = error/EINTR
  // A descriptor that is not open is not readiness.
  if ((lFds[0].revents or lFds[1].revents) and POLLNVAL) <> 0 then Exit;
  if (lFds[1].revents and cReadReady) <> 0 then begin  // a Send
    while __read(fWakeRd, @lJunk, SizeOf(lJunk)) > 0 do ;
    Lock; Unlock;                             // synchronise with the writer
    Exit;                                     // report "no read data"
  end;
  Result := (lFds[0].revents and cReadReady) <> 0;
end;

-- PART 3: WaitForWrite, 4.1.2.355 - 4.1.3.357 -------------------------------
function tPosixSocket.WaitForWrite(aMSec: Cardinal): Boolean;
var
  lFds: array[0..1] of pollfd;
  lRes: Integer;
  lJunk: array[0..63] of Byte;
begin
  Result := False;
  if fSocket = cInvalidSocket then Exit;
  while __read(fWakeRd, @lJunk, SizeOf(lJunk)) > 0 do ;
  if TakePendingWake then Exit;
  lFds[0].fd := fSocket;
  lFds[0].events := POLLOUT;
  lFds[0].revents := 0;
  lFds[1].fd := fWakeRd;
  lFds[1].events := POLLIN;
  lFds[1].revents := 0;
  lRes := poll(@lFds[0], 2, PollTimeout(aMSec));
  if lRes <= 0 then Exit;                     // 0 = timeout, <0 = error/EINTR
  if ((lFds[0].revents or lFds[1].revents) and POLLNVAL) <> 0 then Exit;
  if (lFds[1].revents and cReadReady) <> 0 then begin  // a Wake
    while __read(fWakeRd, @lJunk, SizeOf(lJunk)) > 0 do ;
    TakePendingWake;
    Exit;                                     // woken, not writable
  end;
  Result := (lFds[0].revents and cWriteReady) <> 0;
end;
Top

:: Written with and Powered by StreamSec Tools 4.1 ::
Copyright © 2000-2026 StreamSec Handelsbolag
Forum data layer and page templates derived from the RealThinClient SDK web-forum example, Copyright © 2004-2022 Teppi Technology (MIT)