Date: Tue, 14 Feb 2017 19:13:28 +0000 (UTC) From: Dmitry Chagin <dchagin@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r313740 - head/sys/compat/linux Message-ID: <201702141913.v1EJDSLF001731@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: dchagin Date: Tue Feb 14 19:13:27 2017 New Revision: 313740 URL: https://svnweb.freebsd.org/changeset/base/313740 Log: Replace Linuxulator implementation of readdir(), getdents() and getdents64() with wrapper over kern_getdirentries(). The patch was originally written by emaste@ and then adapted by trasz@ and me. Note: 1. I divided linux_getdents() and linux_readdir() as in case when the getdents() called with count = 1 (readdir() case) it can overwrite user stack (by writing to user buffer pointer more than 1 byte). 2. Linux returns EINVAL in case when user supplied buffer is not enough to contain fetched dirent. 3. Linux returns ENOTDIR in case when fd points to not a directory. Reviewed by: trasz@ MFC after: 1 month Differential Revision: https://reviews.freebsd.org/D2210 Modified: head/sys/compat/linux/linux_file.c Modified: head/sys/compat/linux/linux_file.c ============================================================================== --- head/sys/compat/linux/linux_file.c Tue Feb 14 18:34:25 2017 (r313739) +++ head/sys/compat/linux/linux_file.c Tue Feb 14 19:13:27 2017 (r313740) @@ -53,8 +53,6 @@ __FBSDID("$FreeBSD$"); #include <sys/unistd.h> #include <sys/vnode.h> -#include <security/mac/mac_framework.h> - #ifdef COMPAT_LINUX32 #include <machine/../linux32/linux.h> #include <machine/../linux32/linux32_proto.h> @@ -66,6 +64,10 @@ __FBSDID("$FreeBSD$"); #include <compat/linux/linux_util.h> #include <compat/linux/linux_file.h> +static int linux_common_open(struct thread *, int, char *, int, int); +static int linux_getdents_error(struct thread *, int, int); + + int linux_creat(struct thread *td, struct linux_creat_args *args) { @@ -244,28 +246,41 @@ linux_llseek(struct thread *td, struct l td->td_retval[0] = 0; return (0); } - -int -linux_readdir(struct thread *td, struct linux_readdir_args *args) -{ - struct linux_getdents_args lda; - - lda.fd = args->fd; - lda.dent = args->dent; - lda.count = 1; - return (linux_getdents(td, &lda)); -} #endif /* __i386__ || (__amd64__ && COMPAT_LINUX32) */ /* * Note that linux_getdents(2) and linux_getdents64(2) have the same * arguments. They only differ in the definition of struct dirent they - * operate on. We use this to common the code, with the exception of - * accessing struct dirent. Note that linux_readdir(2) is implemented - * by means of linux_getdents(2). In this case we never operate on - * struct dirent64 and thus don't need to handle it... + * operate on. + * Note that linux_readdir(2) is a special case of linux_getdents(2) + * where count is always equals 1, meaning that the buffer is one + * dirent-structure in size and that the code can't handle more anyway. + * Note that linux_readdir(2) can't be implemented by means of linux_getdents(2) + * as in case when the *dent buffer size is equal to 1 linux_getdents(2) will + * trash user stack. */ +static int +linux_getdents_error(struct thread *td, int fd, int err) +{ + cap_rights_t rights; + struct vnode *vp; + struct file *fp; + int error; + + /* Linux return ENOTDIR in case when fd is not a directory. */ + error = getvnode(td, fd, cap_rights_init(&rights, CAP_READ), &fp); + if (error != 0) + return (error); + vp = fp->f_vnode; + if (vp->v_type != VDIR) { + fdrop(fp, td); + return (ENOTDIR); + } + fdrop(fp, td); + return (err); +} + struct l_dirent { l_ulong d_ino; l_off_t d_off; @@ -292,242 +307,228 @@ struct l_dirent64 { roundup(offsetof(struct l_dirent64, d_name) + (namlen) + 1, \ sizeof(uint64_t)) -#define LINUX_MAXRECLEN max(LINUX_RECLEN(LINUX_NAME_MAX), \ - LINUX_RECLEN64(LINUX_NAME_MAX)) #define LINUX_DIRBLKSIZ 512 -static int -getdents_common(struct thread *td, struct linux_getdents64_args *args, - int is64bit) +/* + * Linux l_dirent is bigger than FreeBSD dirent, thus the buffer size + * passed to kern_getdirentries() must be smaller than the one passed + * to linux_getdents() by certain factor. + */ +#define LINUX_RECLEN_RATIO(X) X * offsetof(struct dirent, d_name) / \ + offsetof(struct l_dirent, d_name); +#define LINUX_RECLEN64_RATIO(X) X * offsetof(struct dirent, d_name) / \ + offsetof(struct l_dirent64, d_name); + +int +linux_getdents(struct thread *td, struct linux_getdents_args *args) { struct dirent *bdp; - struct vnode *vp; caddr_t inp, buf; /* BSD-format */ int len, reclen; /* BSD-format */ caddr_t outp; /* Linux-format */ - int resid, linuxreclen=0; /* Linux-format */ + int resid, linuxreclen; /* Linux-format */ caddr_t lbuf; /* Linux-format */ - cap_rights_t rights; - struct file *fp; - struct uio auio; - struct iovec aiov; - off_t off; + long base; struct l_dirent *linux_dirent; - struct l_dirent64 *linux_dirent64; - int buflen, error, eofflag, nbytes, justone; - u_long *cookies = NULL, *cookiep; - int ncookies; - - nbytes = args->count; - if (nbytes == 1) { - /* readdir(2) case. Always struct dirent. */ - if (is64bit) - return (EINVAL); - nbytes = sizeof(*linux_dirent); - justone = 1; - } else - justone = 0; + int buflen, error; + size_t retval; - error = getvnode(td, args->fd, cap_rights_init(&rights, CAP_READ), &fp); - if (error != 0) - return (error); - - if ((fp->f_flag & FREAD) == 0) { - fdrop(fp, td); - return (EBADF); - } +#ifdef DEBUG + if (ldebug(getdents)) + printf(ARGS(getdents, "%d, *, %d"), args->fd, args->count); +#endif + buflen = LINUX_RECLEN_RATIO(args->count); + buflen = min(buflen, MAXBSIZE); + buf = malloc(buflen, M_TEMP, M_WAITOK); - off = foffset_lock(fp, 0); - vp = fp->f_vnode; - if (vp->v_type != VDIR) { - foffset_unlock(fp, off, 0); - fdrop(fp, td); - return (EINVAL); + error = kern_getdirentries(td, args->fd, buf, buflen, + &base, NULL, UIO_SYSSPACE); + if (error != 0) { + error = linux_getdents_error(td, args->fd, error); + goto out1; } + lbuf = malloc(LINUX_RECLEN(LINUX_NAME_MAX), M_TEMP, M_WAITOK | M_ZERO); - buflen = max(LINUX_DIRBLKSIZ, nbytes); - buflen = min(buflen, MAXBSIZE); - buf = malloc(buflen, M_LINUX, M_WAITOK); - lbuf = malloc(LINUX_MAXRECLEN, M_LINUX, M_WAITOK | M_ZERO); - vn_lock(vp, LK_SHARED | LK_RETRY); - - aiov.iov_base = buf; - aiov.iov_len = buflen; - auio.uio_iov = &aiov; - auio.uio_iovcnt = 1; - auio.uio_rw = UIO_READ; - auio.uio_segflg = UIO_SYSSPACE; - auio.uio_td = td; - auio.uio_resid = buflen; - auio.uio_offset = off; - -#ifdef MAC - /* - * Do directory search MAC check using non-cached credentials. - */ - if ((error = mac_vnode_check_readdir(td->td_ucred, vp))) - goto out; -#endif /* MAC */ - if ((error = VOP_READDIR(vp, &auio, fp->f_cred, &eofflag, &ncookies, - &cookies))) - goto out; - + len = td->td_retval[0]; inp = buf; - outp = (caddr_t)args->dirent; - resid = nbytes; - if ((len = buflen - auio.uio_resid) <= 0) - goto eof; - - cookiep = cookies; - - if (cookies) { - /* - * When using cookies, the vfs has the option of reading from - * a different offset than that supplied (UFS truncates the - * offset to a block boundary to make sure that it never reads - * partway through a directory entry, even if the directory - * has been compacted). - */ - while (len > 0 && ncookies > 0 && *cookiep <= off) { - bdp = (struct dirent *) inp; - len -= bdp->d_reclen; - inp += bdp->d_reclen; - cookiep++; - ncookies--; - } - } + outp = (caddr_t)args->dent; + resid = args->count; + retval = 0; while (len > 0) { - if (cookiep && ncookies == 0) - break; bdp = (struct dirent *) inp; reclen = bdp->d_reclen; - if (reclen & 3) { - error = EFAULT; + linuxreclen = LINUX_RECLEN(bdp->d_namlen); + /* + * No more space in the user supplied dirent buffer. + * Return EINVAL. + */ + if (resid < linuxreclen) { + error = EINVAL; goto out; } - if (bdp->d_fileno == 0) { - inp += reclen; - if (cookiep) { - off = *cookiep++; - ncookies--; - } else - off += reclen; - - len -= reclen; - continue; - } - - linuxreclen = (is64bit) - ? LINUX_RECLEN64(bdp->d_namlen) - : LINUX_RECLEN(bdp->d_namlen); - - if (reclen > len || resid < linuxreclen) { - outp++; - break; - } - - if (justone) { - /* readdir(2) case. */ - linux_dirent = (struct l_dirent*)lbuf; - linux_dirent->d_ino = bdp->d_fileno; - linux_dirent->d_off = (l_off_t)linuxreclen; - linux_dirent->d_reclen = (l_ushort)bdp->d_namlen; - strlcpy(linux_dirent->d_name, bdp->d_name, - linuxreclen - offsetof(struct l_dirent, d_name)); - error = copyout(linux_dirent, outp, linuxreclen); - } - if (is64bit) { - linux_dirent64 = (struct l_dirent64*)lbuf; - linux_dirent64->d_ino = bdp->d_fileno; - linux_dirent64->d_off = (cookiep) - ? (l_off_t)*cookiep - : (l_off_t)(off + reclen); - linux_dirent64->d_reclen = (l_ushort)linuxreclen; - linux_dirent64->d_type = bdp->d_type; - strlcpy(linux_dirent64->d_name, bdp->d_name, - linuxreclen - offsetof(struct l_dirent64, d_name)); - error = copyout(linux_dirent64, outp, linuxreclen); - } else if (!justone) { - linux_dirent = (struct l_dirent*)lbuf; - linux_dirent->d_ino = bdp->d_fileno; - linux_dirent->d_off = (cookiep) - ? (l_off_t)*cookiep - : (l_off_t)(off + reclen); - linux_dirent->d_reclen = (l_ushort)linuxreclen; - /* - * Copy d_type to last byte of l_dirent buffer - */ - lbuf[linuxreclen-1] = bdp->d_type; - strlcpy(linux_dirent->d_name, bdp->d_name, - linuxreclen - offsetof(struct l_dirent, d_name)-1); - error = copyout(linux_dirent, outp, linuxreclen); - } - - if (error) + linux_dirent = (struct l_dirent*)lbuf; + linux_dirent->d_ino = bdp->d_fileno; + linux_dirent->d_off = base + reclen; + linux_dirent->d_reclen = linuxreclen; + /* + * Copy d_type to last byte of l_dirent buffer + */ + lbuf[linuxreclen - 1] = bdp->d_type; + strlcpy(linux_dirent->d_name, bdp->d_name, + linuxreclen - offsetof(struct l_dirent, d_name)-1); + error = copyout(linux_dirent, outp, linuxreclen); + if (error != 0) goto out; inp += reclen; - if (cookiep) { - off = *cookiep++; - ncookies--; - } else - off += reclen; + base += reclen; + len -= reclen; + retval += linuxreclen; outp += linuxreclen; resid -= linuxreclen; - len -= reclen; - if (justone) - break; } - - if (outp == (caddr_t)args->dirent) { - nbytes = resid; - goto eof; - } - - if (justone) - nbytes = resid + linuxreclen; - -eof: - td->td_retval[0] = nbytes - resid; + td->td_retval[0] = retval; out: - free(cookies, M_TEMP); - - VOP_UNLOCK(vp, 0); - foffset_unlock(fp, off, 0); - fdrop(fp, td); - free(buf, M_LINUX); free(lbuf, M_LINUX); +out1: + free(buf, M_LINUX); return (error); } int -linux_getdents(struct thread *td, struct linux_getdents_args *args) +linux_getdents64(struct thread *td, struct linux_getdents64_args *args) { + struct dirent *bdp; + caddr_t inp, buf; /* BSD-format */ + int len, reclen; /* BSD-format */ + caddr_t outp; /* Linux-format */ + int resid, linuxreclen; /* Linux-format */ + caddr_t lbuf; /* Linux-format */ + long base; + struct l_dirent64 *linux_dirent64; + int buflen, error; + size_t retval; #ifdef DEBUG - if (ldebug(getdents)) - printf(ARGS(getdents, "%d, *, %d"), args->fd, args->count); + if (ldebug(getdents64)) + uprintf(ARGS(getdents64, "%d, *, %d"), args->fd, args->count); #endif + buflen = LINUX_RECLEN64_RATIO(args->count); + buflen = min(buflen, MAXBSIZE); + buf = malloc(buflen, M_TEMP, M_WAITOK); - return (getdents_common(td, (struct linux_getdents64_args*)args, 0)); + error = kern_getdirentries(td, args->fd, buf, buflen, + &base, NULL, UIO_SYSSPACE); + if (error != 0) { + error = linux_getdents_error(td, args->fd, error); + goto out1; + } + + lbuf = malloc(LINUX_RECLEN64(LINUX_NAME_MAX), M_TEMP, M_WAITOK | M_ZERO); + + len = td->td_retval[0]; + inp = buf; + outp = (caddr_t)args->dirent; + resid = args->count; + retval = 0; + + while (len > 0) { + bdp = (struct dirent *) inp; + reclen = bdp->d_reclen; + linuxreclen = LINUX_RECLEN64(bdp->d_namlen); + /* + * No more space in the user supplied dirent buffer. + * Return EINVAL. + */ + if (resid < linuxreclen) { + error = EINVAL; + goto out; + } + + linux_dirent64 = (struct l_dirent64*)lbuf; + linux_dirent64->d_ino = bdp->d_fileno; + linux_dirent64->d_off = base + reclen; + linux_dirent64->d_reclen = linuxreclen; + linux_dirent64->d_type = bdp->d_type; + strlcpy(linux_dirent64->d_name, bdp->d_name, + linuxreclen - offsetof(struct l_dirent64, d_name)); + error = copyout(linux_dirent64, outp, linuxreclen); + if (error != 0) + goto out; + + inp += reclen; + base += reclen; + len -= reclen; + + retval += linuxreclen; + outp += linuxreclen; + resid -= linuxreclen; + } + td->td_retval[0] = retval; + +out: + free(lbuf, M_TEMP); +out1: + free(buf, M_TEMP); + return (error); } +#if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) int -linux_getdents64(struct thread *td, struct linux_getdents64_args *args) +linux_readdir(struct thread *td, struct linux_readdir_args *args) { + struct dirent *bdp; + caddr_t buf; /* BSD-format */ + int linuxreclen; /* Linux-format */ + caddr_t lbuf; /* Linux-format */ + long base; + struct l_dirent *linux_dirent; + int buflen, error; #ifdef DEBUG - if (ldebug(getdents64)) - printf(ARGS(getdents64, "%d, *, %d"), args->fd, args->count); + if (ldebug(readdir)) + printf(ARGS(readdir, "%d, *"), args->fd); #endif + buflen = LINUX_RECLEN(LINUX_NAME_MAX); + buflen = LINUX_RECLEN_RATIO(buflen); + buf = malloc(buflen, M_TEMP, M_WAITOK); + + error = kern_getdirentries(td, args->fd, buf, buflen, + &base, NULL, UIO_SYSSPACE); + if (error != 0) { + error = linux_getdents_error(td, args->fd, error); + goto out; + } + if (td->td_retval[0] == 0) + goto out; - return (getdents_common(td, args, 1)); + lbuf = malloc(LINUX_RECLEN(LINUX_NAME_MAX), M_TEMP, M_WAITOK | M_ZERO); + + bdp = (struct dirent *) buf; + linuxreclen = LINUX_RECLEN(bdp->d_namlen); + + linux_dirent = (struct l_dirent*)lbuf; + linux_dirent->d_ino = bdp->d_fileno; + linux_dirent->d_off = linuxreclen; + linux_dirent->d_reclen = bdp->d_namlen; + strlcpy(linux_dirent->d_name, bdp->d_name, + linuxreclen - offsetof(struct l_dirent, d_name)); + error = copyout(linux_dirent, args->dent, linuxreclen); + if (error == 0) + td->td_retval[0] = linuxreclen; + + free(lbuf, M_LINUX); +out: + free(buf, M_LINUX); + return (error); } +#endif /* __i386__ || (__amd64__ && COMPAT_LINUX32) */ + /* * These exist mainly for hooks for doing /compat/linux translation.
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201702141913.v1EJDSLF001731>