X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/0a7de7458d150b5d4dffc935ba399be265ef0a1a..a991bd8d3e7fe02dbca0644054bab73c5b75324a:/bsd/kern/tty_ptmx.c diff --git a/bsd/kern/tty_ptmx.c b/bsd/kern/tty_ptmx.c index 6da6e641a..d4efb5c12 100644 --- a/bsd/kern/tty_ptmx.c +++ b/bsd/kern/tty_ptmx.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997-2013 Apple Inc. All rights reserved. + * Copyright (c) 1997-2019 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -117,19 +117,39 @@ extern d_reset_t ptcreset; extern d_select_t ptcselect; static int ptmx_major; /* dynamically assigned major number */ -static struct cdevsw ptmx_cdev = { - ptcopen, ptcclose, ptcread, ptcwrite, - ptyioctl, ptcstop, ptcreset, 0, - ptcselect, eno_mmap, eno_strat, eno_getc, - eno_putc, D_TTY +static const struct cdevsw ptmx_cdev = { + .d_open = ptcopen, + .d_close = ptcclose, + .d_read = ptcread, + .d_write = ptcwrite, + .d_ioctl = ptyioctl, + .d_stop = ptcstop, + .d_reset = ptcreset, + .d_ttys = NULL, + .d_select = ptcselect, + .d_mmap = eno_mmap, + .d_strategy = eno_strat, + .d_reserved_1 = eno_getc, + .d_reserved_2 = eno_putc, + .d_type = D_TTY }; static int ptsd_major; /* dynamically assigned major number */ -static struct cdevsw ptsd_cdev = { - ptsopen, ptsclose, ptsread, ptswrite, - ptyioctl, ptsstop, ptsreset, 0, - ptsselect, eno_mmap, eno_strat, eno_getc, - eno_putc, D_TTY +static const struct cdevsw ptsd_cdev = { + .d_open = ptsopen, + .d_close = ptsclose, + .d_read = ptsread, + .d_write = ptswrite, + .d_ioctl = ptyioctl, + .d_stop = ptsstop, + .d_reset = ptsreset, + .d_ttys = NULL, + .d_select = ptsselect, + .d_mmap = eno_mmap, + .d_strategy = eno_strat, + .d_reserved_1 = eno_getc, + .d_reserved_2 = eno_putc, + .d_type = D_TTY }; /* @@ -249,9 +269,12 @@ static struct _ptmx_ioctl_state { static struct ptmx_ioctl * ptmx_get_ioctl(int minor, int open_flag) { - struct ptmx_ioctl *new_ptmx_ioctl; + struct ptmx_ioctl *ptmx_ioctl = NULL; if (open_flag & PF_OPEN_M) { + struct ptmx_ioctl *new_ptmx_ioctl; + + DEVFS_LOCK(); /* * If we are about to allocate more memory, but we have * already hit the administrative limit, then fail the @@ -262,8 +285,10 @@ ptmx_get_ioctl(int minor, int open_flag) * snapping to the nearest PTMX_GROW_VECTOR... */ if ((_state.pis_total - _state.pis_free) >= ptmx_max) { + DEVFS_UNLOCK(); return NULL; } + DEVFS_UNLOCK(); MALLOC(new_ptmx_ioctl, struct ptmx_ioctl *, sizeof(struct ptmx_ioctl), M_TTYS, M_WAITOK | M_ZERO); if (new_ptmx_ioctl == NULL) { @@ -282,6 +307,18 @@ ptmx_get_ioctl(int minor, int open_flag) * doing so avoids a reallocation race on the minor number. */ DEVFS_LOCK(); + + /* + * Check again to ensure the limit is not reached after initial check + * when the lock was dropped momentarily for malloc. + */ + if ((_state.pis_total - _state.pis_free) >= ptmx_max) { + ttyfree(new_ptmx_ioctl->pt_tty); + DEVFS_UNLOCK(); + FREE(new_ptmx_ioctl, M_TTYS); + return NULL; + } + /* Need to allocate a larger vector? */ if (_state.pis_free == 0) { struct ptmx_ioctl **new_pis_ioctl_list; @@ -345,11 +382,17 @@ ptmx_get_ioctl(int minor, int open_flag) } } - if (minor < 0 || minor >= _state.pis_total) { - return NULL; + /* + * Lock is held here to protect race when the 'pis_ioctl_list' array is + * being reallocated to increase its slots. + */ + DEVFS_LOCK(); + if (minor >= 0 && minor < _state.pis_total) { + ptmx_ioctl = _state.pis_ioctl_list[minor]; } + DEVFS_UNLOCK(); - return _state.pis_ioctl_list[minor]; + return ptmx_ioctl; } /* @@ -467,8 +510,8 @@ ptmx_clone(__unused dev_t dev, int action) int ptsd_kqfilter(dev_t dev, struct knote *kn); static void ptsd_kqops_detach(struct knote *); static int ptsd_kqops_event(struct knote *, long); -static int ptsd_kqops_touch(struct knote *kn, struct kevent_internal_s *kev); -static int ptsd_kqops_process(struct knote *kn, struct filt_process_s *data, struct kevent_internal_s *kev); +static int ptsd_kqops_touch(struct knote *kn, struct kevent_qos_s *kev); +static int ptsd_kqops_process(struct knote *kn, struct kevent_qos_s *kev); SECURITY_READ_ONLY_EARLY(struct filterops) ptsd_kqops = { .f_isfd = 1, @@ -491,10 +534,7 @@ SECURITY_READ_ONLY_EARLY(struct filterops) ptsd_kqops = { static void ptsd_kqops_detach(struct knote *kn) { - struct tty *tp; - - tp = kn->kn_hook; - assert(tp != NULL); + struct tty *tp = kn->kn_hook; tty_lock(tp); @@ -507,42 +547,41 @@ ptsd_kqops_detach(struct knote *kn) case EVFILT_READ: KNOTE_DETACH(&tp->t_rsel.si_note, kn); break; - case EVFILT_WRITE: KNOTE_DETACH(&tp->t_wsel.si_note, kn); break; - default: panic("invalid knote %p detach, filter: %d", kn, kn->kn_filter); break; } } - kn->kn_hook = NULL; tty_unlock(tp); - ttyfree(tp); } static int -ptsd_kqops_common(struct knote *kn, struct tty *tp) +ptsd_kqops_common(struct knote *kn, struct kevent_qos_s *kev, struct tty *tp) { int retval = 0; + int64_t data = 0; TTY_LOCK_OWNED(tp); switch (kn->kn_filter) { case EVFILT_READ: - kn->kn_data = ttnread(tp); - if (kn->kn_data > 0) { - retval = 1; - } + /* + * ttnread can change the tty state, + * hence must be done upfront, before any other check. + */ + data = ttnread(tp); + retval = (data > 0); break; case EVFILT_WRITE: if ((tp->t_outq.c_cc <= tp->t_lowat) && (tp->t_state & TS_CONNECTED)) { - kn->kn_data = tp->t_outq.c_cn - tp->t_outq.c_cc; + data = tp->t_outq.c_cn - tp->t_outq.c_cc; retval = 1; } break; @@ -555,9 +594,13 @@ ptsd_kqops_common(struct knote *kn, struct tty *tp) if (tp->t_state & TS_ZOMBIE) { kn->kn_flags |= EV_EOF; + } + if (kn->kn_flags & EV_EOF) { retval = 1; } - + if (retval && kev) { + knote_fill_kevent(kn, kev, data); + } return retval; } @@ -566,35 +609,25 @@ ptsd_kqops_event(struct knote *kn, long hint) { struct tty *tp = kn->kn_hook; int ret; - bool revoked = hint & NOTE_REVOKE; - hint &= ~NOTE_REVOKE; - if (!hint) { - tty_lock(tp); - } + TTY_LOCK_OWNED(tp); - if (revoked) { + if (hint & NOTE_REVOKE) { kn->kn_flags |= EV_EOF | EV_ONESHOT; ret = 1; } else { - ret = ptsd_kqops_common(kn, tp); - } - - if (!hint) { - tty_unlock(tp); + ret = ptsd_kqops_common(kn, NULL, tp); } return ret; } static int -ptsd_kqops_touch(struct knote *kn, struct kevent_internal_s *kev) +ptsd_kqops_touch(struct knote *kn, struct kevent_qos_s *kev) { - struct tty *tp; + struct tty *tp = kn->kn_hook; int ret; - tp = kn->kn_hook; - tty_lock(tp); /* accept new kevent state */ @@ -602,7 +635,7 @@ ptsd_kqops_touch(struct knote *kn, struct kevent_internal_s *kev) kn->kn_sdata = kev->data; /* recapture fired state of knote */ - ret = ptsd_kqops_common(kn, tp); + ret = ptsd_kqops_common(kn, NULL, tp); tty_unlock(tp); @@ -610,21 +643,13 @@ ptsd_kqops_touch(struct knote *kn, struct kevent_internal_s *kev) } static int -ptsd_kqops_process(struct knote *kn, __unused struct filt_process_s *data, - struct kevent_internal_s *kev) +ptsd_kqops_process(struct knote *kn, struct kevent_qos_s *kev) { struct tty *tp = kn->kn_hook; int ret; tty_lock(tp); - ret = ptsd_kqops_common(kn, tp); - if (ret) { - *kev = kn->kn_kevent; - if (kn->kn_flags & EV_CLEAR) { - kn->kn_fflags = 0; - kn->kn_data = 0; - } - } + ret = ptsd_kqops_common(kn, kev, tp); tty_unlock(tp); return ret; @@ -672,7 +697,7 @@ ptsd_kqfilter(dev_t dev, struct knote *kn) } /* capture current event state */ - ret = ptsd_kqops_common(kn, tp); + ret = ptsd_kqops_common(kn, NULL, tp); tty_unlock(tp); @@ -688,10 +713,12 @@ ptsd_revoke_knotes(__unused int minor, struct tty *tp) tty_lock(tp); ttwakeup(tp); - KNOTE(&tp->t_rsel.si_note, NOTE_REVOKE | 1 /* the lock is already held */); + assert((tp->t_rsel.si_flags & SI_KNPOSTING) == 0); + KNOTE(&tp->t_rsel.si_note, NOTE_REVOKE); ttwwakeup(tp); - KNOTE(&tp->t_wsel.si_note, NOTE_REVOKE | 1); + assert((tp->t_wsel.si_flags & SI_KNPOSTING) == 0); + KNOTE(&tp->t_wsel.si_note, NOTE_REVOKE); tty_unlock(tp); } @@ -706,9 +733,10 @@ ptsd_revoke_knotes(__unused int minor, struct tty *tp) int ptmx_kqfilter(dev_t dev, struct knote *kn); static void ptmx_kqops_detach(struct knote *); static int ptmx_kqops_event(struct knote *, long); -static int ptmx_kqops_touch(struct knote *kn, struct kevent_internal_s *kev); -static int ptmx_kqops_process(struct knote *kn, struct filt_process_s *data, struct kevent_internal_s *kev); -static int ptmx_kqops_common(struct knote *kn, struct ptmx_ioctl *pti, struct tty *tp); +static int ptmx_kqops_touch(struct knote *kn, struct kevent_qos_s *kev); +static int ptmx_kqops_process(struct knote *kn, struct kevent_qos_s *kev); +static int ptmx_kqops_common(struct knote *kn, struct kevent_qos_s *kev, + struct ptmx_ioctl *pti, struct tty *tp); SECURITY_READ_ONLY_EARLY(struct filterops) ptmx_kqops = { .f_isfd = 1, @@ -728,8 +756,7 @@ ptmx_knote_ioctl(struct knote *kn) static struct tty * ptmx_knote_tty(struct knote *kn) { - struct ptmx_ioctl *pti = kn->kn_hook; - return pti->pt_tty; + return ptmx_knote_ioctl(kn)->pt_tty; } int @@ -754,6 +781,8 @@ ptmx_kqfilter(dev_t dev, struct knote *kn) tty_lock(tp); kn->kn_filtid = EVFILTID_PTMX; + /* the tty will be freed when detaching the knote */ + ttyhold(tp); kn->kn_hook = pti; /* @@ -775,10 +804,8 @@ ptmx_kqfilter(dev_t dev, struct knote *kn) } /* capture current event state */ - ret = ptmx_kqops_common(kn, pti, tp); + ret = ptmx_kqops_common(kn, NULL, pti, tp); - /* take a reference on the TTY */ - ttyhold(tp); tty_unlock(tp); return ret; @@ -790,49 +817,39 @@ ptmx_kqops_detach(struct knote *kn) struct ptmx_ioctl *pti = kn->kn_hook; struct tty *tp = pti->pt_tty; - assert(tp != NULL); - tty_lock(tp); switch (kn->kn_filter) { case EVFILT_READ: KNOTE_DETACH(&pti->pt_selr.si_note, kn); break; - case EVFILT_WRITE: KNOTE_DETACH(&pti->pt_selw.si_note, kn); break; - default: panic("invalid knote %p detach, filter: %d", kn, kn->kn_filter); break; } - kn->kn_hook = NULL; tty_unlock(tp); - ttyfree(tp); } static int -ptmx_kqops_common(struct knote *kn, struct ptmx_ioctl *pti, struct tty *tp) +ptmx_kqops_common(struct knote *kn, struct kevent_qos_s *kev, + struct ptmx_ioctl *pti, struct tty *tp) { int retval = 0; + int64_t data = 0; TTY_LOCK_OWNED(tp); - /* disconnects should force a wakeup (EOF) */ - if (!(tp->t_state & TS_CONNECTED)) { - kn->kn_flags |= EV_EOF; - return 1; - } - switch (kn->kn_filter) { case EVFILT_READ: /* there's data on the TTY and it's not stopped */ if (tp->t_outq.c_cc && !(tp->t_state & TS_TTSTOP)) { - retval = tp->t_outq.c_cc; - kn->kn_data = retval; + data = tp->t_outq.c_cc; + retval = data > 0; } else if (((pti->pt_flags & PF_PKT) && pti->pt_send) || ((pti->pt_flags & PF_UCNTL) && pti->pt_ucntl)) { retval = 1; @@ -861,11 +878,16 @@ ptmx_kqops_common(struct knote *kn, struct ptmx_ioctl *pti, struct tty *tp) break; } - if (tp->t_state & TS_ZOMBIE) { + /* disconnects should force a wakeup (EOF) */ + if (!(tp->t_state & TS_CONNECTED) || (tp->t_state & TS_ZOMBIE)) { kn->kn_flags |= EV_EOF; + } + if (kn->kn_flags & EV_EOF) { retval = 1; } - + if (retval && kev) { + knote_fill_kevent(kn, kev, data); + } return retval; } @@ -875,29 +897,21 @@ ptmx_kqops_event(struct knote *kn, long hint) struct ptmx_ioctl *pti = ptmx_knote_ioctl(kn); struct tty *tp = ptmx_knote_tty(kn); int ret; - bool revoked = hint & NOTE_REVOKE; - hint &= ~NOTE_REVOKE; - if (!hint) { - tty_lock(tp); - } + TTY_LOCK_OWNED(tp); - if (revoked) { + if (hint & NOTE_REVOKE) { kn->kn_flags |= EV_EOF | EV_ONESHOT; ret = 1; } else { - ret = ptmx_kqops_common(kn, pti, tp); - } - - if (!hint) { - tty_unlock(tp); + ret = ptmx_kqops_common(kn, NULL, pti, tp); } return ret; } static int -ptmx_kqops_touch(struct knote *kn, struct kevent_internal_s *kev) +ptmx_kqops_touch(struct knote *kn, struct kevent_qos_s *kev) { struct ptmx_ioctl *pti = ptmx_knote_ioctl(kn); struct tty *tp = ptmx_knote_tty(kn); @@ -910,7 +924,7 @@ ptmx_kqops_touch(struct knote *kn, struct kevent_internal_s *kev) kn->kn_sdata = kev->data; /* recapture fired state of knote */ - ret = ptmx_kqops_common(kn, pti, tp); + ret = ptmx_kqops_common(kn, NULL, pti, tp); tty_unlock(tp); @@ -918,22 +932,14 @@ ptmx_kqops_touch(struct knote *kn, struct kevent_internal_s *kev) } static int -ptmx_kqops_process(struct knote *kn, __unused struct filt_process_s *data, - struct kevent_internal_s *kev) +ptmx_kqops_process(struct knote *kn, struct kevent_qos_s *kev) { struct ptmx_ioctl *pti = ptmx_knote_ioctl(kn); struct tty *tp = ptmx_knote_tty(kn); int ret; tty_lock(tp); - ret = ptmx_kqops_common(kn, pti, tp); - if (ret) { - *kev = kn->kn_kevent; - if (kn->kn_flags & EV_CLEAR) { - kn->kn_fflags = 0; - kn->kn_data = 0; - } - } + ret = ptmx_kqops_common(kn, kev, pti, tp); tty_unlock(tp); return ret;