Skip to content

unix: guard fd-touching port methods against use after Close#223

Open
ultramcu wants to merge 1 commit into
bugst:masterfrom
ultramcu:fix/unix-use-after-close-guard
Open

unix: guard fd-touching port methods against use after Close#223
ultramcu wants to merge 1 commit into
bugst:masterfrom
ultramcu:fix/unix-use-after-close-guard

Conversation

@ultramcu
Copy link
Copy Markdown
Contributor

Problem

On the unix backend, only Read() checked whether the port was still open before touching the file descriptor. Write, Break, SetMode, SetDTR, SetRTS, GetModemStatusBits, Drain, ResetInputBuffer and ResetOutputBuffer issued their read/write/ioctl directly on port.handle.

After Close() the fd is closed and its integer number may be reassigned by the OS to an entirely unrelated file, so a late or concurrent call could:

  • operate on the wrong fd (e.g. Write silently succeeds against a reused descriptor — silent data corruption), or
  • return a confusing raw EBADF ("bad file descriptor") instead of a clear "port closed" error.

Related to (but distinct from) #150 — that issue is about Close not unblocking a blocked Write; this is about the missing open-check on the non-Read methods.

Fix

Add a small withGuard helper that mirrors the existing Read() guard: it takes the close-lock read side and verifies port.opened == 1 before running the operation, returning PortError{PortClosed} otherwise. Every fd-touching method is wrapped with it (Write keeps the guard inline because it returns (int, error)). SetReadTimeout is intentionally left unguarded as it only mutates an in-memory field.

Test

serial_postclose_linux_test.go opens a pseudo-terminal (no real serial hardware needed; skips cleanly if /dev/ptmx is unavailable), closes the port, then asserts every method returns PortError{PortClosed}. Verified fail-before/pass-after on Linux: without the guards the test fails with "bad file descriptor" on all methods; with them it passes. Builds and go vet clean on linux/darwin/freebsd/openbsd/windows; gofmt clean; -race clean.

Note: serial_bsd.go's build tag includes dragonfly/netbsd, but the package already does not compile on those platforms (unixPort is undefined there at HEAD), so the added withGuard call introduces no new breakage.

Only Read() checked whether the port was still open before touching the file
descriptor. Write, Break, SetMode, SetDTR, SetRTS, GetModemStatusBits, Drain,
ResetInputBuffer and ResetOutputBuffer issued their read/write/ioctl directly
on port.handle. After Close() the fd is closed and its number may be reassigned
by the OS to an unrelated file, so a late or concurrent call could operate on
the wrong fd (silent data corruption) or return a confusing EBADF instead of a
clear "port closed" error. Related to (but distinct from) bugst#150.

Add a small withGuard helper mirroring the existing Read() guard: it takes the
close-lock read side and verifies the port is still open before running the
operation, returning PortError{PortClosed} otherwise. Every fd-touching method
is wrapped with it (Write keeps the guard inline because it returns
(int, error)). SetReadTimeout is intentionally left unguarded as it only
mutates an in-memory field.

serial_postclose_linux_test.go opens a pseudo-terminal (no real serial hardware
needed; skips cleanly if /dev/ptmx is unavailable), closes the port, then
asserts every method returns PortError{PortClosed}. Without the guards the test
fails with "bad file descriptor" on all methods; with them it passes.
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.

1 participant