Weekly Padgett

No. 0351 min read

select() loops: ready does not mean done, folks

The corkscrew audit found ordinary socket-relay mistakes hiding behind a small loop. Short writes, signals, descriptor limits, and a timeout that never quits.

A person holds a long sandwich extending through a door mail slot beside office computers.
File photoDramatization. No corkscrew was harmed.

Corkscrew[1]Ref. 1Corkscrew en.wikipedia.org's relay job is to copy standard input to a socket, copy the socket to standard output, and stop when everybody is finished talking. You can explain it that quickly and still fuck up most of the implementation.

I want the evidence straight. The audit reproduced handshake framing failures. Reading the source turned up ignored partial writes and missing signal handling, along with an unchecked descriptor limit. Those cases were not dynamically reproduced. I take the source findings seriously, but I'm not going to call them successful attacks.

The select(2) manual[2]Ref. 2select(2) manual man7.org describes readiness. A readable socket might have some bytes, or it might be at EOF. A writable descriptor does not promise that whatever enormous write you feel like handing it will finish without blocking. That's a fairly important bit of the damned promise.

A write-all helper keeps you from forgetting the unwritten suffix. It still leaves the scheduling to us. For a reliable duplex relay I would use nonblocking I/O[3]Ref. 3nonblocking I/O en.wikipedia.org with separate pending buffers for each direction. The readiness requests would depend on what those buffers actually need.

I'd read only while the corresponding output buffer has room, and watch for writability only when something is queued. When the buffer fills, I'd stop accepting input in that direction. That is backpressure[4]Ref. 4backpressure en.wikipedia.org, and it keeps a slow reader from turning your small program into an unbounded memory allocation.

The write(2) contract[5]Ref. 5write(2) contract man7.org allows a successful call to write fewer bytes than requested. A positive result tells you how many bytes went through. I want to see what the loop does with the bytes still waiting.

References

  1. Corkscrew en.wikipedia.org ↩
  2. select(2) manual man7.org ↩
  3. nonblocking I/O en.wikipedia.org ↩
  4. backpressure en.wikipedia.org ↩
  5. write(2) contract man7.org ↩
corkscrewcunixnetworking

More on this beat