Release 4.7 fs/compat.c
/*
* linux/fs/compat.c
*
* Kernel compatibililty routines for e.g. 32 bit syscall support
* on 64 bit kernels.
*
* Copyright (C) 2002 Stephen Rothwell, IBM Corporation
* Copyright (C) 1997-2000 Jakub Jelinek (jakub@redhat.com)
* Copyright (C) 1998 Eddie C. Dost (ecd@skynet.be)
* Copyright (C) 2001,2002 Andi Kleen, SuSE Labs
* Copyright (C) 2003 Pavel Machek (pavel@ucw.cz)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/stddef.h>
#include <linux/kernel.h>
#include <linux/linkage.h>
#include <linux/compat.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/namei.h>
#include <linux/file.h>
#include <linux/fdtable.h>
#include <linux/vfs.h>
#include <linux/ioctl.h>
#include <linux/init.h>
#include <linux/ncp_mount.h>
#include <linux/nfs4_mount.h>
#include <linux/syscalls.h>
#include <linux/ctype.h>
#include <linux/dirent.h>
#include <linux/fsnotify.h>
#include <linux/highuid.h>
#include <linux/personality.h>
#include <linux/rwsem.h>
#include <linux/tsacct_kern.h>
#include <linux/security.h>
#include <linux/highmem.h>
#include <linux/signal.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/fs_struct.h>
#include <linux/slab.h>
#include <linux/pagemap.h>
#include <linux/aio.h>
#include <asm/uaccess.h>
#include <asm/mmu_context.h>
#include <asm/ioctls.h>
#include "internal.h"
int compat_log = 1;
int compat_printk(const char *fmt, ...)
{
va_list ap;
int ret;
if (!compat_log)
return 0;
va_start(ap, fmt);
ret = vprintk(fmt, ap);
va_end(ap);
return ret;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
andi kleen | andi kleen | 50 | 100.00% | 1 | 100.00% |
| Total | 50 | 100.00% | 1 | 100.00% |
/*
* Not all architectures have sys_utime, so implement this in terms
* of sys_utimes.
*/
COMPAT_SYSCALL_DEFINE2(utime, const char __user *, filename,
struct compat_utimbuf __user *, t)
{
struct timespec tv[2];
if (t) {
if (get_user(tv[0].tv_sec, &t->actime) ||
get_user(tv[1].tv_sec, &t->modtime))
return -EFAULT;
tv[0].tv_nsec = 0;
tv[1].tv_nsec = 0;
}
return do_utimes(AT_FDCWD, filename, t ? tv : NULL, 0);
}
COMPAT_SYSCALL_DEFINE4(utimensat, unsigned int, dfd, const char __user *, filename, struct compat_timespec __user *, t, int, flags)
{
struct timespec tv[2];
if (t) {
if (compat_get_timespec(&tv[0], &t[0]) ||
compat_get_timespec(&tv[1], &t[1]))
return -EFAULT;
if (tv[0].tv_nsec == UTIME_OMIT && tv[1].tv_nsec == UTIME_OMIT)
return 0;
}
return do_utimes(dfd, filename, t ? tv : NULL, flags);
}
COMPAT_SYSCALL_DEFINE3(futimesat, unsigned int, dfd, const char __user *, filename, struct compat_timeval __user *, t)
{
struct timespec tv[2];
if (t) {
if (get_user(tv[0].tv_sec, &t[0].tv_sec) ||
get_user(tv[0].tv_nsec, &t[0].tv_usec) ||
get_user(tv[1].tv_sec, &t[1].tv_sec) ||
get_user(tv[1].tv_nsec, &t[1].tv_usec))
return -EFAULT;
if (tv[0].tv_nsec >= 1000000 || tv[0].tv_nsec < 0 ||
tv[1].tv_nsec >= 1000000 || tv[1].tv_nsec < 0)
return -EINVAL;
tv[0].tv_nsec *= 1000;
tv[1].tv_nsec *= 1000;
}
return do_utimes(dfd, filename, t ? tv : NULL, 0);
}
COMPAT_SYSCALL_DEFINE2(utimes, const char __user *, filename, struct compat_timeval __user *, t)
{
return compat_sys_futimesat(AT_FDCWD, filename, t);
}
static int cp_compat_stat(struct kstat *stat, struct compat_stat __user *ubuf)
{
struct compat_stat tmp;
if (!old_valid_dev(stat->dev) || !old_valid_dev(stat->rdev))
return -EOVERFLOW;
memset(&tmp, 0, sizeof(tmp));
tmp.st_dev = old_encode_dev(stat->dev);
tmp.st_ino = stat->ino;
if (sizeof(tmp.st_ino) < sizeof(stat->ino) && tmp.st_ino != stat->ino)
return -EOVERFLOW;
tmp.st_mode = stat->mode;
tmp.st_nlink = stat->nlink;
if (tmp.st_nlink != stat->nlink)
return -EOVERFLOW;
SET_UID(tmp.st_uid, from_kuid_munged(current_user_ns(), stat->uid));
SET_GID(tmp.st_gid, from_kgid_munged(current_user_ns(), stat->gid));
tmp.st_rdev = old_encode_dev(stat->rdev);
if ((u64) stat->size > MAX_NON_LFS)
return -EOVERFLOW;
tmp.st_size = stat->size;
tmp.st_atime = stat->atime.tv_sec;
tmp.st_atime_nsec = stat->atime.tv_nsec;
tmp.st_mtime = stat->mtime.tv_sec;
tmp.st_mtime_nsec = stat->mtime.tv_nsec;
tmp.st_ctime = stat->ctime.tv_sec;
tmp.st_ctime_nsec = stat->ctime.tv_nsec;
tmp.st_blocks = stat->blocks;
tmp.st_blksize = stat->blksize;
return copy_to_user(ubuf, &tmp, sizeof(tmp)) ? -EFAULT : 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
al viro | al viro | 144 | 48.65% | 1 | 33.33% |
christoph hellwig | christoph hellwig | 140 | 47.30% | 1 | 33.33% |
eric w. biederman | eric w. biederman | 12 | 4.05% | 1 | 33.33% |
| Total | 296 | 100.00% | 3 | 100.00% |
COMPAT_SYSCALL_DEFINE2(newstat, const char __user *, filename,
struct compat_stat __user *, statbuf)
{
struct kstat stat;
int error;
error = vfs_stat(filename, &stat);
if (error)
return error;
return cp_compat_stat(&stat, statbuf);
}
COMPAT_SYSCALL_DEFINE2(newlstat, const char __user *, filename,
struct compat_stat __user *, statbuf)
{
struct kstat stat;
int error;
error = vfs_lstat(filename, &stat);
if (error)
return error;
return cp_compat_stat(&stat, statbuf);
}
#ifndef __ARCH_WANT_STAT64
COMPAT_SYSCALL_DEFINE4(newfstatat, unsigned int, dfd,
const char __user *, filename,
struct compat_stat __user *, statbuf, int, flag)
{
struct kstat stat;
int error;
error = vfs_fstatat(dfd, filename, &stat, flag);
if (error)
return error;
return cp_compat_stat(&stat, statbuf);
}
#endif
COMPAT_SYSCALL_DEFINE2(newfstat, unsigned int, fd,
struct compat_stat __user *, statbuf)
{
struct kstat stat;
int error = vfs_fstat(fd, &stat);
if (!error)
error = cp_compat_stat(&stat, statbuf);
return error;
}
static int put_compat_statfs(struct compat_statfs __user *ubuf, struct kstatfs *kbuf)
{
if (sizeof ubuf->f_blocks == 4) {
if ((kbuf->f_blocks | kbuf->f_bfree | kbuf->f_bavail |
kbuf->f_bsize | kbuf->f_frsize) & 0xffffffff00000000ULL)
return -EOVERFLOW;
/* f_files and f_ffree may be -1; it's okay
* to stuff that into 32 bits */
if (kbuf->f_files != 0xffffffffffffffffULL
&& (kbuf->f_files & 0xffffffff00000000ULL))
return -EOVERFLOW;
if (kbuf->f_ffree != 0xffffffffffffffffULL
&& (kbuf->f_ffree & 0xffffffff00000000ULL))
return -EOVERFLOW;
}
if (!access_ok(VERIFY_WRITE, ubuf, sizeof(*ubuf)) ||
__put_user(kbuf->f_type, &ubuf->f_type) ||
__put_user(kbuf->f_bsize, &ubuf->f_bsize) ||
__put_user(kbuf->f_blocks, &ubuf->f_blocks) ||
__put_user(kbuf->f_bfree, &ubuf->f_bfree) ||
__put_user(kbuf->f_bavail, &ubuf->f_bavail) ||
__put_user(kbuf->f_files, &ubuf->f_files) ||
__put_user(kbuf->f_ffree, &ubuf->f_ffree) ||
__put_user(kbuf->f_namelen, &ubuf->f_namelen) ||
__put_user(kbuf->f_fsid.val[0], &ubuf->f_fsid.val[0]) ||
__put_user(kbuf->f_fsid.val[1], &ubuf->f_fsid.val[1]) ||
__put_user(kbuf->f_frsize, &ubuf->f_frsize) ||
__put_user(kbuf->f_flags, &ubuf->f_flags) ||
__clear_user(ubuf->f_spare, sizeof(ubuf->f_spare)))
return -EFAULT;
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
stephen rothwell | stephen rothwell | 176 | 58.67% | 1 | 12.50% |
andrew morton | andrew morton | 51 | 17.00% | 1 | 12.50% |
olaf kirch | olaf kirch | 36 | 12.00% | 1 | 12.50% |
andi kleen | andi kleen | 20 | 6.67% | 1 | 12.50% |
jon tollefson | jon tollefson | 8 | 2.67% | 1 | 12.50% |
eric w. biederman | eric w. biederman | 6 | 2.00% | 1 | 12.50% |
jesper juhl | jesper juhl | 2 | 0.67% | 1 | 12.50% |
linus torvalds | linus torvalds | 1 | 0.33% | 1 | 12.50% |
| Total | 300 | 100.00% | 8 | 100.00% |
/*
* The following statfs calls are copies of code from fs/statfs.c and
* should be checked against those from time to time
*/
COMPAT_SYSCALL_DEFINE2(statfs, const char __user *, pathname, struct compat_statfs __user *, buf)
{
struct kstatfs tmp;
int error = user_statfs(pathname, &tmp);
if (!error)
error = put_compat_statfs(buf, &tmp);
return error;
}
COMPAT_SYSCALL_DEFINE2(fstatfs, unsigned int, fd, struct compat_statfs __user *, buf)
{
struct kstatfs tmp;
int error = fd_statfs(fd, &tmp);
if (!error)
error = put_compat_statfs(buf, &tmp);
return error;
}
static int put_compat_statfs64(struct compat_statfs64 __user *ubuf, struct kstatfs *kbuf)
{
if (sizeof ubuf->f_blocks == 4) {
if ((kbuf->f_blocks | kbuf->f_bfree | kbuf->f_bavail |
kbuf->f_bsize | kbuf->f_frsize) & 0xffffffff00000000ULL)
return -EOVERFLOW;
/* f_files and f_ffree may be -1; it's okay
* to stuff that into 32 bits */
if (kbuf->f_files != 0xffffffffffffffffULL
&& (kbuf->f_files & 0xffffffff00000000ULL))
return -EOVERFLOW;
if (kbuf->f_ffree != 0xffffffffffffffffULL
&& (kbuf->f_ffree & 0xffffffff00000000ULL))
return -EOVERFLOW;
}
if (!access_ok(VERIFY_WRITE, ubuf, sizeof(*ubuf)) ||
__put_user(kbuf->f_type, &ubuf->f_type) ||
__put_user(kbuf->f_bsize, &ubuf->f_bsize) ||
__put_user(kbuf->f_blocks, &ubuf->f_blocks) ||
__put_user(kbuf->f_bfree, &ubuf->f_bfree) ||
__put_user(kbuf->f_bavail, &ubuf->f_bavail) ||
__put_user(kbuf->f_files, &ubuf->f_files) ||
__put_user(kbuf->f_ffree, &ubuf->f_ffree) ||
__put_user(kbuf->f_namelen, &ubuf->f_namelen) ||
__put_user(kbuf->f_fsid.val[0], &ubuf->f_fsid.val[0]) ||
__put_user(kbuf->f_fsid.val[1], &ubuf->f_fsid.val[1]) ||
__put_user(kbuf->f_frsize, &ubuf->f_frsize) ||
__put_user(kbuf->f_flags, &ubuf->f_flags) ||
__clear_user(ubuf->f_spare, sizeof(ubuf->f_spare)))
return -EFAULT;
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
andi kleen | andi kleen | 227 | 75.67% | 1 | 16.67% |
olaf kirch | olaf kirch | 36 | 12.00% | 1 | 16.67% |
namhyung kim | namhyung kim | 26 | 8.67% | 1 | 16.67% |
jon tollefson | jon tollefson | 8 | 2.67% | 1 | 16.67% |
jesper juhl | jesper juhl | 2 | 0.67% | 1 | 16.67% |
linus torvalds | linus torvalds | 1 | 0.33% | 1 | 16.67% |
| Total | 300 | 100.00% | 6 | 100.00% |
COMPAT_SYSCALL_DEFINE3(statfs64, const char __user *, pathname, compat_size_t, sz, struct compat_statfs64 __user *, buf)
{
struct kstatfs tmp;
int error;
if (sz != sizeof(*buf))
return -EINVAL;
error = user_statfs(pathname, &tmp);
if (!error)
error = put_compat_statfs64(buf, &tmp);
return error;
}
COMPAT_SYSCALL_DEFINE3(fstatfs64, unsigned int, fd, compat_size_t, sz, struct compat_statfs64 __user *, buf)
{
struct kstatfs tmp;
int error;
if (sz != sizeof(*buf))
return -EINVAL;
error = fd_statfs(fd, &tmp);
if (!error)
error = put_compat_statfs64(buf, &tmp);
return error;
}
/*
* This is a copy of sys_ustat, just dealing with a structure layout.
* Given how simple this syscall is that apporach is more maintainable
* than the various conversion hacks.
*/
COMPAT_SYSCALL_DEFINE2(ustat, unsigned, dev, struct compat_ustat __user *, u)
{
struct compat_ustat tmp;
struct kstatfs sbuf;
int err = vfs_ustat(new_decode_dev(dev), &sbuf);
if (err)
return err;
memset(&tmp, 0, sizeof(struct compat_ustat));
tmp.f_tfree = sbuf.f_bfree;
tmp.f_tinode = sbuf.f_ffree;
if (copy_to_user(u, &tmp, sizeof(struct compat_ustat)))
return -EFAULT;
return 0;
}
static int get_compat_flock(struct flock *kfl, struct compat_flock __user *ufl)
{
if (!access_ok(VERIFY_READ, ufl, sizeof(*ufl)) ||
__get_user(kfl->l_type, &ufl->l_type) ||
__get_user(kfl->l_whence, &ufl->l_whence) ||
__get_user(kfl->l_start, &ufl->l_start) ||
__get_user(kfl->l_len, &ufl->l_len) ||
__get_user(kfl->l_pid, &ufl->l_pid))
return -EFAULT;
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
stephen rothwell | stephen rothwell | 99 | 99.00% | 1 | 50.00% |
linus torvalds | linus torvalds | 1 | 1.00% | 1 | 50.00% |
| Total | 100 | 100.00% | 2 | 100.00% |
static int put_compat_flock(struct flock *kfl, struct compat_flock __user *ufl)
{
if (!access_ok(VERIFY_WRITE, ufl, sizeof(*ufl)) ||
__put_user(kfl->l_type, &ufl->l_type) ||
__put_user(kfl->l_whence, &ufl->l_whence) ||
__put_user(kfl->l_start, &ufl->l_start) ||
__put_user(kfl->l_len, &ufl->l_len) ||
__put_user(kfl->l_pid, &ufl->l_pid))
return -EFAULT;
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
stephen rothwell | stephen rothwell | 99 | 99.00% | 1 | 50.00% |
linus torvalds | linus torvalds | 1 | 1.00% | 1 | 50.00% |
| Total | 100 | 100.00% | 2 | 100.00% |
#ifndef HAVE_ARCH_GET_COMPAT_FLOCK64
static int get_compat_flock64(struct flock *kfl, struct compat_flock64 __user *ufl)
{
if (!access_ok(VERIFY_READ, ufl, sizeof(*ufl)) ||
__get_user(kfl->l_type, &ufl->l_type) ||
__get_user(kfl->l_whence, &ufl->l_whence) ||
__get_user(kfl->l_start, &ufl->l_start) ||
__get_user(kfl->l_len, &ufl->l_len) ||
__get_user(kfl->l_pid, &ufl->l_pid))
return -EFAULT;
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
stephen rothwell | stephen rothwell | 99 | 99.00% | 1 | 50.00% |
linus torvalds | linus torvalds | 1 | 1.00% | 1 | 50.00% |
| Total | 100 | 100.00% | 2 | 100.00% |
#endif
#ifndef HAVE_ARCH_PUT_COMPAT_FLOCK64
static int put_compat_flock64(struct flock *kfl, struct compat_flock64 __user *ufl)
{
if (!access_ok(VERIFY_WRITE, ufl, sizeof(*ufl)) ||
__put_user(kfl->l_type, &ufl->l_type) ||
__put_user(kfl->l_whence, &ufl->l_whence) ||
__put_user(kfl->l_start, &ufl->l_start) ||
__put_user(kfl->l_len, &ufl->l_len) ||
__put_user(kfl->l_pid, &ufl->l_pid))
return -EFAULT;
return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
stephen rothwell | stephen rothwell | 99 | 99.00% | 1 | 50.00% |
linus torvalds | linus torvalds | 1 | 1.00% | 1 | 50.00% |
| Total | 100 | 100.00% | 2 | 100.00% |
#endif
static unsigned int
convert_fcntl_cmd(unsigned int cmd)
{
switch (cmd) {
case F_GETLK64:
return F_GETLK;
case F_SETLK64:
return F_SETLK;
case F_SETLKW64:
return F_SETLKW;
}
return cmd;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp |
jeff layton | jeff layton | 38 | 100.00% | 1 | 100.00% |
| Total | 38 | 100.00% | 1 | 100.00% |
COMPAT_SYSCALL_DEFINE3(fcntl64, unsigned int, fd, unsigned int, cmd,
compat_ulong_t, arg)
{
mm_segment_t old_fs;
struct flock f;
long ret;
unsigned int conv_cmd;
switch (cmd) {
case F_GETLK:
case F_SETLK:
case F_SETLKW:
ret = get_compat_flock(&f, compat_ptr(arg));
if (ret != 0)
break;
old_fs = get_fs();
set_fs(KERNEL_DS);
ret = sys_fcntl(fd, cmd, (unsigned long)&f);
set_fs(old_fs);
if (cmd == F_GETLK && ret == 0) {
/* GETLK was successful and we need to return the data...
* but it needs to fit in the compat structure.
* l_start shouldn't be too big, unless the original
* start + end is greater than COMPAT_OFF_T_MAX, in which
* case the app was asking for trouble, so we return
* -EOVERFLOW in that case.
* l_len could be too big, in which case we just truncate it,
* and only allow the app to see that part of the conflicting
* lock that might make sense to it anyway
*/
if (f.l_start > COMPAT_OFF_T_MAX)
ret = -EOVERFLOW;
if (f.l_len > COMPAT_OFF_T_MAX)
f.l_len = COMPAT_OFF_T_MAX;
if (ret == 0)
ret = put_compat_flock(&f, compat_ptr(arg));
}
break;
case F_GETLK64:
case F_SETLK64:
case F_SETLKW64:
case F_OFD_GETLK:
case F_OFD_SETLK:
case F_OFD_SETLKW:
ret = get_compat_flock64(&f, compat_ptr(arg));
if (ret != 0)
break;
old_fs = get_fs();
set_fs(KERNEL_DS);
conv_cmd = convert_fcntl_cmd(cmd);
ret = sys_fcntl(fd, conv_cmd, (unsigned long)&f);
set_fs(old_fs);
if ((conv_cmd == F_GETLK || conv_cmd == F_OFD_GETLK) && ret == 0) {
/* need to return lock information - see above for commentary */
if (f.l_start > COMPAT_LOFF_T_MAX)
ret = -EOVERFLOW;
if (f.l_len > COMPAT_LOFF_T_MAX)
f.l_len = COMPAT_LOFF_T_MAX;
if (ret == 0)
ret = put_compat_flock64(&f, compat_ptr(arg));
}
break;
default:
ret = sys_fcntl(fd, cmd, arg);
break;
}
return ret;
}
COMPAT_SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd,
compat_ulong_t, arg)
{
switch (cmd) {
case F_GETLK64:
case F_SETLK64:
case F_SETLKW64:
case F_OFD_GETLK:
case F_OFD_SETLK:
case F_OFD_SETLKW:
return -EINVAL;
}
return compat_sys_fcntl64(fd, cmd, arg);
}
COMPAT_SYSCALL_DEFINE2(io_setup, unsigned, nr_reqs, u32 __user *, ctx32p)
{
long ret;
aio_context_t ctx64;
mm_segment_t oldfs = get_fs();
if (unlikely(get_user(ctx64, ctx32p)))
return -EFAULT;
set_fs(KERNEL_DS);
/* The __user pointer cast is valid because of the set_fs() */
ret = sys_io_setup(nr_reqs, (aio_context_t __user *) &ctx64);
set_fs(oldfs);
/* truncating is ok because it's a user address */
if (!ret)
ret = put_user((u32) ctx64, ctx32p);
return ret;
}
COMPAT_SYSCALL_DEFINE5(io_getevents, compat_aio_context_t, ctx_id,
compat_long_t, min_nr,
compat_long_t, nr,
struct io_event __user *