aboutsummaryrefslogtreecommitdiff
path: root/sys/compat/linux/linux_file.c
diff options
context:
space:
mode:
authorDmitry Chagin <dchagin@FreeBSD.org>2017-02-14 19:13:27 +0000
committerDmitry Chagin <dchagin@FreeBSD.org>2017-02-14 19:13:27 +0000
commit3a66cf037230a8c1df5a58f75c165a11f4496054 (patch)
treecb5e1bc9521490eefb390136e37300f2e6fb0afb /sys/compat/linux/linux_file.c
parent33d9db92e277d510f3c402580f1aebe954962945 (diff)
downloadsrc-3a66cf037230a8c1df5a58f75c165a11f4496054.tar.gz
src-3a66cf037230a8c1df5a58f75c165a11f4496054.zip
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
Notes
Notes: svn path=/head/; revision=313740
Diffstat (limited to 'sys/compat/linux/linux_file.c')
-rw-r--r--sys/compat/linux/linux_file.c407
1 files changed, 204 insertions, 203 deletions
diff --git a/sys/compat/linux/linux_file.c b/sys/compat/linux/linux_file.c
index 7c44009724b5..a7695f010cee 100644
--- a/sys/compat/linux/linux_file.c
+++ b/sys/compat/linux/linux_file.c
@@ -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 linux_llseek_args *args)
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.