diff options
author | Konstantin Belousov <kib@FreeBSD.org> | 2019-07-21 15:07:12 +0000 |
---|---|---|
committer | Konstantin Belousov <kib@FreeBSD.org> | 2019-07-21 15:07:12 +0000 |
commit | f1cf2b9dcb9a4e8cf8295e97eeaa4add96e80412 (patch) | |
tree | d5fcf8602ad5925792cac5ce475ee1d48f94583d /sys/kern/uipc_usrreq.c | |
parent | 349c8683227737360dfef03187c02a582203e75c (diff) | |
download | src-f1cf2b9dcb9a4e8cf8295e97eeaa4add96e80412.tar.gz src-f1cf2b9dcb9a4e8cf8295e97eeaa4add96e80412.zip |
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
Notes
Notes:
svn path=/head/; revision=350199
Diffstat (limited to 'sys/kern/uipc_usrreq.c')
-rw-r--r-- | sys/kern/uipc_usrreq.c | 20 |
1 files changed, 16 insertions, 4 deletions
diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c index 0ffa9d3a22ba..1a875d7e5ed4 100644 --- a/sys/kern/uipc_usrreq.c +++ b/sys/kern/uipc_usrreq.c @@ -2154,7 +2154,7 @@ unp_internalize(struct mbuf **controlp, struct thread *td) 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 *td) 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.")); } |