git: c4290f346662 - stable/14 - kern: restore signal mask before ast() for pselect/ppoll
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 10 Dec 2024 20:03:18 UTC
The branch stable/14 has been updated by kevans: URL: https://cgit.FreeBSD.org/src/commit/?id=c4290f3466624f8e26af83f3090c3b0f62512ca1 commit c4290f3466624f8e26af83f3090c3b0f62512ca1 Author: Kyle Evans <kevans@FreeBSD.org> AuthorDate: 2024-11-26 04:04:48 +0000 Commit: Kyle Evans <kevans@FreeBSD.org> CommitDate: 2024-12-10 20:02:52 +0000 kern: restore signal mask before ast() for pselect/ppoll It's possible to take a signal after pselect/ppoll have set their return value, but before we actually return to userland. This results in taking a signal without reflecting it in the return value, which weakens the guarantees provided by these functions. Switch both to restore the signal mask before we would deliver signals on return to userland. If a signal was received after the wait was over, then we'll just have the signal queued up for the next time it comes unblocked. The modified signal mask is retained if we were interrupted so that ast() actually handles the signal, at which point the signal mask is restored. des@ has a test case demonstrating the issue in D47738 which will follow. Reported by: des Reviewed by: des (earlier version), kib Sponsored by: Klara, Inc. Sponsored by: NetApp, Inc. (cherry picked from commit ccb973da1f1b65879eade8e65cdd2885e125f90e) --- sys/kern/sys_generic.c | 53 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/sys/kern/sys_generic.c b/sys/kern/sys_generic.c index 77e4c13d4390..6a80ddbe86ed 100644 --- a/sys/kern/sys_generic.c +++ b/sys/kern/sys_generic.c @@ -1050,15 +1050,31 @@ kern_pselect(struct thread *td, int nd, fd_set *in, fd_set *ou, fd_set *ex, &td->td_oldsigmask, 0); if (error != 0) return (error); - td->td_pflags |= TDP_OLDMASK; + } + error = kern_select(td, nd, in, ou, ex, tvp, abi_nfdbits); + if (uset != NULL) { /* * Make sure that ast() is called on return to * usermode and TDP_OLDMASK is cleared, restoring old - * sigmask. + * sigmask. If we didn't get interrupted, then the caller is + * likely not expecting a signal to hit that should normally be + * blocked by its signal mask, so we restore the mask before + * any signals could be delivered. */ - ast_sched(td, TDA_SIGSUSPEND); + if (error == EINTR) { + td->td_pflags |= TDP_OLDMASK; + ast_sched(td, TDA_SIGSUSPEND); + } else { + int serror __diagused; + + /* *select(2) should never restart. */ + MPASS(error != ERESTART); + serror = kern_sigprocmask(td, SIG_SETMASK, + &td->td_oldsigmask, NULL, 0); + MPASS(serror == 0); + } } - error = kern_select(td, nd, in, ou, ex, tvp, abi_nfdbits); + return (error); } @@ -1529,13 +1545,6 @@ kern_poll_kfds(struct thread *td, struct pollfd *kfds, u_int nfds, &td->td_oldsigmask, 0); if (error) return (error); - td->td_pflags |= TDP_OLDMASK; - /* - * Make sure that ast() is called on return to - * usermode and TDP_OLDMASK is cleared, restoring old - * sigmask. - */ - ast_sched(td, TDA_SIGSUSPEND); } seltdinit(td); @@ -1558,6 +1567,28 @@ kern_poll_kfds(struct thread *td, struct pollfd *kfds, u_int nfds, error = EINTR; if (error == EWOULDBLOCK) error = 0; + + if (uset != NULL) { + /* + * Make sure that ast() is called on return to + * usermode and TDP_OLDMASK is cleared, restoring old + * sigmask. If we didn't get interrupted, then the caller is + * likely not expecting a signal to hit that should normally be + * blocked by its signal mask, so we restore the mask before + * any signals could be delivered. + */ + if (error == EINTR) { + td->td_pflags |= TDP_OLDMASK; + ast_sched(td, TDA_SIGSUSPEND); + } else { + int serror __diagused; + + serror = kern_sigprocmask(td, SIG_SETMASK, + &td->td_oldsigmask, NULL, 0); + MPASS(serror == 0); + } + } + return (error); }