Skip to content

drvAsynIPServerPort: bound the UDP read by the datagram, not by maxchars#233

Merged
MarkRivers merged 1 commit into
epics-modules:masterfrom
physwkim:fix/ip-server-udp-read
Jul 16, 2026
Merged

drvAsynIPServerPort: bound the UDP read by the datagram, not by maxchars#233
MarkRivers merged 1 commit into
epics-modules:masterfrom
physwkim:fix/ip-server-udp-read

Conversation

@physwkim

Copy link
Copy Markdown
Contributor

readIt in the UDP path copies from the staged datagram buffer using the caller's maxchars as the bound rather than the number of bytes actually received (UDPbufferSize). Three faults follow from that one mistake:

  1. Stale-heap / info leak. With a 256-byte caller buffer and a 10-byte datagram, the loop copies 255 bytes: 10 real, then 245 bytes of the previous datagram's tail — or, on the first datagram after connect, never-written malloc memory. All 255 are reported as received via *nbytesTransfered.
  2. Off-by-one loss. The copy takes maxchars - 1 bytes but UDPbufferPos advances by maxchars, so one byte of every datagram is silently dropped.
  3. maxchars == 1 deadlock. The loop body never runs, thisRead = 0, but the position still advances by 1 — a byte-at-a-time reader makes no progress.

Fixed structurally: the staged datagram occupies [UDPbufferPos, UDPbufferSize); deliver min(remaining, maxchars) and advance by exactly what was delivered. The existing UDPbufferSize <= UDPbufferPos test then selects EOM_END/EOM_CNT unchanged.

Verified against a real UDP port on loopback read via asynOctetSyncIO:

case before after
200-byte datagram, maxchars=4096 reported 4095 200
2-byte "HI", maxchars=64 63 bytes incl. stale heap 2, "HI"
"ABCDEFGHIJ" split over reads "ABCEFGIJ…" (dropped bytes) "ABCDEFGHIJ"
"XY", maxchars=1 "" (no progress) "XY"

readIt() copied from the staged datagram with a loop bounded by the caller's
buffer size instead of by the number of bytes actually received, and then
advanced UDPbufferPos by a different amount again.  Three consequences:

- The copy was never bounded by UDPbufferSize.  Reading a 10-byte datagram
  with a 256-byte buffer copied 255 bytes -- 10 real ones followed by 245
  bytes of the previous datagram's tail, or, on the first read after connect,
  of never-written malloc() memory -- and reported all 255 through
  *nbytesTransferred.  Any device support that trusts the transfer count
  rather than scanning for an EOS terminator parses that as received data.

- The loop copied maxchars-1 bytes but UDPbufferPos advanced by maxchars,
  so one byte of every datagram was skipped.

- maxchars == 1 copied nothing yet still consumed a byte, so a byte-at-a-time
  reader never made progress.

UDPbufferSize is the datagram length returned by recvfrom(); it was read only
after the copy, to pick the EOM reason, and never bounded the loop.

Treat the staged datagram as occupying [UDPbufferPos, UDPbufferSize): deliver
min(remaining, maxchars) bytes and advance UDPbufferPos by exactly what was
delivered.  The datagram is then exhausted precisely when UDPbufferPos reaches
UDPbufferSize, which is the existing test that picks ASYN_EOM_END over
ASYN_EOM_CNT, so the EOM and buffer-release semantics are unchanged.  A read
that exactly fills the caller's buffer now leaves thisRead == maxchars, and
the trailing "if there is room add a null byte" test correctly skips the null
and reports ASYN_EOM_CNT.

Verified against a real UDP port on the loopback interface, reading through
asynOctetSyncIO:

  before:
    200-byte datagram, read with maxchars=4096  -> reported 4095 bytes
    2-byte datagram "HI", read with maxchars=64 -> reported 63 bytes:
        "HICRET-PASSWORD-SECRET-PASSWORD-SECRET-PASSWORD-SECRET-PASSWORD"
        (61 bytes of the preceding datagram, reported as received)
    "ABCDEFGHIJ" read 4 bytes at a time          -> "ABCEFGIJS"
    "XY" read 1 byte at a time                   -> "" (0 bytes, both consumed)
  after:
    200-byte datagram, read with maxchars=4096  -> 200 bytes
    2-byte datagram "HI", read with maxchars=64 -> 2 bytes, "HI"
    "ABCDEFGHIJ" read 4 bytes at a time          -> "ABCDEFGHIJ"
    "XY" read 1 byte at a time                   -> "XY"
@MarkRivers
MarkRivers merged commit fd77dff into epics-modules:master Jul 16, 2026
16 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants