From owner-svn-src-head@freebsd.org Sun Jul 21 15:07:14 2019 Return-Path: Delivered-To: svn-src-head@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id 20CF0B5920; Sun, 21 Jul 2019 15:07:14 +0000 (UTC) (envelope-from kib@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id F155883A5E; Sun, 21 Jul 2019 15:07:13 +0000 (UTC) (envelope-from kib@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id C8A3424FAE; Sun, 21 Jul 2019 15:07:13 +0000 (UTC) (envelope-from kib@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id x6LF7Dll053556; Sun, 21 Jul 2019 15:07:13 GMT (envelope-from kib@FreeBSD.org) Received: (from kib@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id x6LF7C7G053552; Sun, 21 Jul 2019 15:07:12 GMT (envelope-from kib@FreeBSD.org) Message-Id: <201907211507.x6LF7C7G053552@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: kib set sender to kib@FreeBSD.org using -f From: Konstantin Belousov Date: Sun, 21 Jul 2019 15:07:12 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r350199 - in head/sys: kern sys X-SVN-Group: head X-SVN-Commit-Author: kib X-SVN-Commit-Paths: in head/sys: kern sys X-SVN-Commit-Revision: 350199 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Rspamd-Queue-Id: F155883A5E X-Spamd-Bar: -- Authentication-Results: mx1.freebsd.org X-Spamd-Result: default: False [-2.98 / 15.00]; local_wl_from(0.00)[FreeBSD.org]; NEURAL_HAM_MEDIUM(-1.00)[-1.000,0]; NEURAL_HAM_LONG(-1.00)[-1.000,0]; NEURAL_HAM_SHORT(-0.98)[-0.984,0]; ASN(0.00)[asn:11403, ipnet:2610:1c1:1::/48, country:US] X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sun, 21 Jul 2019 15:07:14 -0000 Author: kib Date: Sun Jul 21 15:07:12 2019 New Revision: 350199 URL: https://svnweb.freebsd.org/changeset/base/350199 Log: Check and avoid overflow when incrementing fp->f_count in fget_unlocked() and fhold(). On sufficiently large machine, f_count can be legitimately very large, e.g. malicious code can dup same fd up to the per-process filedescriptors limit, and then fork as much as it can. On some smaller machine, I see kern.maxfilesperproc: 939132 kern.maxprocperuid: 34203 which already overflows u_int. More, the malicious code can create transient references by sending fds over unix sockets. I realized that this check is missed after reading https://secfault-security.com/blog/FreeBSD-SA-1902.fd.html Reviewed by: markj (previous version), mjg Tested by: pho (previous version) Sponsored by: The FreeBSD Foundation MFC after: 1 week Differential revision: https://reviews.freebsd.org/D20947 Modified: head/sys/kern/kern_descrip.c head/sys/kern/sys_generic.c head/sys/kern/uipc_usrreq.c head/sys/sys/file.h head/sys/sys/refcount.h Modified: head/sys/kern/kern_descrip.c ============================================================================== --- head/sys/kern/kern_descrip.c Sun Jul 21 11:40:00 2019 (r350198) +++ head/sys/kern/kern_descrip.c Sun Jul 21 15:07:12 2019 (r350199) @@ -853,6 +853,10 @@ kern_dup(struct thread *td, u_int mode, int flags, int goto unlock; } + oldfde = &fdp->fd_ofiles[old]; + if (!fhold(oldfde->fde_file)) + goto unlock; + /* * If the caller specified a file descriptor, make sure the file * table is large enough to hold it, and grab it. Otherwise, just @@ -861,13 +865,17 @@ kern_dup(struct thread *td, u_int mode, int flags, int switch (mode) { case FDDUP_NORMAL: case FDDUP_FCNTL: - if ((error = fdalloc(td, new, &new)) != 0) + if ((error = fdalloc(td, new, &new)) != 0) { + fdrop(oldfde->fde_file, td); goto unlock; + } break; case FDDUP_MUSTREPLACE: /* Target file descriptor must exist. */ - if (fget_locked(fdp, new) == NULL) + if (fget_locked(fdp, new) == NULL) { + fdrop(oldfde->fde_file, td); goto unlock; + } break; case FDDUP_FIXED: if (new >= fdp->fd_nfiles) { @@ -884,6 +892,7 @@ kern_dup(struct thread *td, u_int mode, int flags, int error = racct_set_unlocked(p, RACCT_NOFILE, new + 1); if (error != 0) { error = EMFILE; + fdrop(oldfde->fde_file, td); goto unlock; } } @@ -899,8 +908,6 @@ kern_dup(struct thread *td, u_int mode, int flags, int KASSERT(old != new, ("new fd is same as old")); - oldfde = &fdp->fd_ofiles[old]; - fhold(oldfde->fde_file); newfde = &fdp->fd_ofiles[new]; delfp = newfde->fde_file; @@ -1901,12 +1908,14 @@ finstall(struct thread *td, struct file *fp, int *fd, MPASS(fd != NULL); + if (!fhold(fp)) + return (EBADF); FILEDESC_XLOCK(fdp); if ((error = fdalloc(td, 0, fd))) { FILEDESC_XUNLOCK(fdp); + fdrop(fp, td); return (error); } - fhold(fp); _finstall(fdp, fp, *fd, flags, fcaps); FILEDESC_XUNLOCK(fdp); return (0); @@ -2047,7 +2056,8 @@ fdcopy(struct filedesc *fdp) for (i = 0; i <= fdp->fd_lastfile; ++i) { ofde = &fdp->fd_ofiles[i]; if (ofde->fde_file == NULL || - (ofde->fde_file->f_ops->fo_flags & DFLAG_PASSABLE) == 0) { + (ofde->fde_file->f_ops->fo_flags & DFLAG_PASSABLE) == 0 || + !fhold(ofde->fde_file)) { if (newfdp->fd_freefile == -1) newfdp->fd_freefile = i; continue; @@ -2055,7 +2065,6 @@ fdcopy(struct filedesc *fdp) nfde = &newfdp->fd_ofiles[i]; *nfde = *ofde; filecaps_copy(&ofde->fde_caps, &nfde->fde_caps, true); - fhold(nfde->fde_file); fdused_init(newfdp, i); newfdp->fd_lastfile = i; } @@ -2108,10 +2117,13 @@ fdcopy_remapped(struct filedesc *fdp, const int *fds, error = EINVAL; goto bad; } + if (!fhold(nfde->fde_file)) { + error = EBADF; + goto bad; + } nfde = &newfdp->fd_ofiles[i]; *nfde = *ofde; filecaps_copy(&ofde->fde_caps, &nfde->fde_caps, true); - fhold(nfde->fde_file); fdused_init(newfdp, i); newfdp->fd_lastfile = i; } @@ -2153,9 +2165,9 @@ fdclearlocks(struct thread *td) (p->p_leader->p_flag & P_ADVLOCK) != 0) { for (i = 0; i <= fdp->fd_lastfile; i++) { fp = fdp->fd_ofiles[i].fde_file; - if (fp == NULL || fp->f_type != DTYPE_VNODE) + if (fp == NULL || fp->f_type != DTYPE_VNODE || + !fhold(fp)) continue; - fhold(fp); FILEDESC_XUNLOCK(fdp); lf.l_whence = SEEK_SET; lf.l_start = 0; @@ -2596,8 +2608,8 @@ fget_cap(struct thread *td, int fd, cap_rights_t *need get_locked: FILEDESC_SLOCK(fdp); error = fget_cap_locked(fdp, fd, needrightsp, fpp, havecapsp); - if (error == 0) - fhold(*fpp); + if (error == 0 && !fhold(*fpp)) + error = EBADF; FILEDESC_SUNLOCK(fdp); #endif return (error); @@ -2656,14 +2668,19 @@ fget_unlocked(struct filedesc *fdp, int fd, cap_rights * table before this fd was closed, so it possible that * there is a stale fp pointer in cached version. */ - fdt = *(const struct fdescenttbl * const volatile *)&(fdp->fd_files); + fdt = *(const struct fdescenttbl * const volatile *) + &(fdp->fd_files); continue; } + if (__predict_false(count + 1 < count)) + return (EBADF); + /* * Use an acquire barrier to force re-reading of fdt so it is * refreshed for verification. */ - if (atomic_fcmpset_acq_int(&fp->f_count, &count, count + 1) == 0) + if (__predict_false(atomic_fcmpset_acq_int(&fp->f_count, + &count, count + 1) == 0)) goto retry; fdt = fdp->fd_files; #ifdef CAPABILITIES @@ -3048,7 +3065,11 @@ dupfdopen(struct thread *td, struct filedesc *fdp, int FILEDESC_XUNLOCK(fdp); return (EACCES); } - fhold(fp); + if (!fhold(fp)) { + fdunused(fdp, indx); + FILEDESC_XUNLOCK(fdp); + return (EBADF); + } newfde = &fdp->fd_ofiles[indx]; oldfde = &fdp->fd_ofiles[dfd]; ioctls = filecaps_copy_prep(&oldfde->fde_caps); Modified: head/sys/kern/sys_generic.c ============================================================================== --- head/sys/kern/sys_generic.c Sun Jul 21 11:40:00 2019 (r350198) +++ head/sys/kern/sys_generic.c Sun Jul 21 15:07:12 2019 (r350199) @@ -757,7 +757,11 @@ kern_ioctl(struct thread *td, int fd, u_long com, cadd fp = NULL; /* fhold() was not called yet */ goto out; } - fhold(fp); + if (!fhold(fp)) { + error = EBADF; + fp = NULL; + goto out; + } if (locked == LA_SLOCKED) { FILEDESC_SUNLOCK(fdp); locked = LA_UNLOCKED; Modified: head/sys/kern/uipc_usrreq.c ============================================================================== --- head/sys/kern/uipc_usrreq.c Sun Jul 21 11:40:00 2019 (r350198) +++ head/sys/kern/uipc_usrreq.c Sun Jul 21 15:07:12 2019 (r350199) @@ -2154,7 +2154,7 @@ unp_internalize(struct mbuf **controlp, struct thread struct timespec *ts; void *data; socklen_t clen, datalen; - int i, error, *fdp, oldfds; + int i, j, error, *fdp, oldfds; u_int newlen; UNP_LINK_UNLOCK_ASSERT(); @@ -2237,6 +2237,19 @@ unp_internalize(struct mbuf **controlp, struct thread goto out; } fdp = data; + for (i = 0; i < oldfds; i++, fdp++) { + if (!fhold(fdesc->fd_ofiles[*fdp].fde_file)) { + fdp = data; + for (j = 0; j < i; j++, fdp++) { + fdrop(fdesc->fd_ofiles[*fdp]. + fde_file, td); + } + FILEDESC_SUNLOCK(fdesc); + error = EBADF; + goto out; + } + } + fdp = data; fdep = (struct filedescent **) CMSG_DATA(mtod(*controlp, struct cmsghdr *)); fdev = malloc(sizeof(*fdev) * oldfds, M_FILECAPS, @@ -2440,7 +2453,6 @@ unp_internalize_fp(struct file *fp) unp->unp_file = fp; unp->unp_msgcount++; } - fhold(fp); unp_rights++; UNP_LINK_WUNLOCK(); } @@ -2601,10 +2613,10 @@ unp_gc(__unused void *arg, int pending) if ((unp->unp_gcflag & UNPGC_DEAD) != 0) { f = unp->unp_file; if (unp->unp_msgcount == 0 || f == NULL || - f->f_count != unp->unp_msgcount) + f->f_count != unp->unp_msgcount || + !fhold(f)) continue; unref[total++] = f; - fhold(f); KASSERT(total <= unp_unreachable, ("unp_gc: incorrect unreachable count.")); } Modified: head/sys/sys/file.h ============================================================================== --- head/sys/sys/file.h Sun Jul 21 11:40:00 2019 (r350198) +++ head/sys/sys/file.h Sun Jul 21 15:07:12 2019 (r350199) @@ -284,8 +284,12 @@ _fnoop(void) return (0); } -#define fhold(fp) \ - (refcount_acquire(&(fp)->f_count)) +static __inline __result_use_check bool +fhold(struct file *fp) +{ + return (refcount_acquire_checked(&fp->f_count)); +} + #define fdrop(fp, td) \ (refcount_release(&(fp)->f_count) ? _fdrop((fp), (td)) : _fnoop()) Modified: head/sys/sys/refcount.h ============================================================================== --- head/sys/sys/refcount.h Sun Jul 21 11:40:00 2019 (r350198) +++ head/sys/sys/refcount.h Sun Jul 21 15:07:12 2019 (r350199) @@ -54,6 +54,20 @@ refcount_acquire(volatile u_int *count) atomic_add_int(count, 1); } +static __inline __result_use_check bool +refcount_acquire_checked(volatile u_int *count) +{ + u_int lcount; + + for (lcount = *count;;) { + if (__predict_false(lcount + 1 < lcount)) + return (false); + if (__predict_true(atomic_fcmpset_int(count, &lcount, + lcount + 1) == 1)) + return (true); + } +} + static __inline int refcount_release(volatile u_int *count) {