Skip to content

epoll: avoid orphaned interest on EventRegister failure#13245

Open
ibondarenko1 wants to merge 1 commit into
google:masterfrom
ibondarenko1:hardening/epoll-addinterest-ordering
Open

epoll: avoid orphaned interest on EventRegister failure#13245
ibondarenko1 wants to merge 1 commit into
google:masterfrom
ibondarenko1:hardening/epoll-addinterest-ordering

Conversation

@ibondarenko1
Copy link
Copy Markdown

Problem

EpollInstance.AddInterest inserts the new epollInterest into ep.interest before registering with the file, and adds it to file.epolls only after registration succeeds:

ep.interest[key] = epi
wmask := waiter.EventMaskFromLinux(mask)
epi.waiter.Init(epi, wmask)
if err := file.EventRegister(&epi.waiter); err != nil {
	return err
}
...
file.epolls[epi] = struct{}{}

If file.EventRegister fails, AddInterest returns with epi in ep.interest but never in file.epolls.

The interest-removal paths rely on ep.interest and file.epolls staying in sync: EpollInstance.Release walks ep.interest, and the FileDescription.DecRef path walks file.epolls. An entry present in ep.interest but not file.epolls is orphaned, and it keeps the target *FileDescription reachable past its own release.

EventRegister fails deterministically for an unprivileged caller. For example, epoll_ctl(EPOLL_CTL_ADD) on an opened-but-unmounted /dev/fuse FD reaches fuse.DeviceFD.EventRegister, which returns EPERM when the device is not connected.

Change

Move ep.interest[key] = epi to after EventRegister succeeds. epi is then added to ep.interest and file.epolls only on the success path, with no fallible call between the two inserts, so a failed registration leaves nothing behind. A short comment records the ordering requirement.

Scope

Hardening. A bounded per-epoll_ctl leak plus a stale *FileDescription reference, reachable by an unprivileged process.

EpollInstance.AddInterest inserts the new epollInterest into ep.interest
before calling file.EventRegister, and adds it to file.epolls only after
EventRegister succeeds. If EventRegister fails, AddInterest returns with
the epollInterest in ep.interest but never in file.epolls.

That breaks the invariant the interest-removal paths rely on:
EpollInstance.Release walks ep.interest and the FileDescription.DecRef
path walks file.epolls. An entry present in one but not the other is
orphaned and pins the target FileDescription. EventRegister fails
deterministically for an unprivileged caller, for example epoll_ctl on
an opened-but-unmounted /dev/fuse FD.

Insert into ep.interest only after EventRegister succeeds.
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