0460b2a28b
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
579 lines
15 KiB
C
579 lines
15 KiB
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/cred.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/mm.h>
|
|
#include <linux/fs_struct.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/pagemap.h>
|
|
#include <linux/aio.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
#include <asm/mmu_context.h>
|
|
#include <asm/ioctls.h>
|
|
#include "internal.h"
|
|
|
|
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;
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
#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;
|
|
}
|
|
#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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* A write operation does a read from user space and vice versa */
|
|
#define vrfy_dir(type) ((type) == READ ? VERIFY_WRITE : VERIFY_READ)
|
|
|
|
ssize_t compat_rw_copy_check_uvector(int type,
|
|
const struct compat_iovec __user *uvector, unsigned long nr_segs,
|
|
unsigned long fast_segs, struct iovec *fast_pointer,
|
|
struct iovec **ret_pointer)
|
|
{
|
|
compat_ssize_t tot_len;
|
|
struct iovec *iov = *ret_pointer = fast_pointer;
|
|
ssize_t ret = 0;
|
|
int seg;
|
|
|
|
/*
|
|
* SuS says "The readv() function *may* fail if the iovcnt argument
|
|
* was less than or equal to 0, or greater than {IOV_MAX}. Linux has
|
|
* traditionally returned zero for zero segments, so...
|
|
*/
|
|
if (nr_segs == 0)
|
|
goto out;
|
|
|
|
ret = -EINVAL;
|
|
if (nr_segs > UIO_MAXIOV)
|
|
goto out;
|
|
if (nr_segs > fast_segs) {
|
|
ret = -ENOMEM;
|
|
iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);
|
|
if (iov == NULL)
|
|
goto out;
|
|
}
|
|
*ret_pointer = iov;
|
|
|
|
ret = -EFAULT;
|
|
if (!access_ok(VERIFY_READ, uvector, nr_segs*sizeof(*uvector)))
|
|
goto out;
|
|
|
|
/*
|
|
* Single unix specification:
|
|
* We should -EINVAL if an element length is not >= 0 and fitting an
|
|
* ssize_t.
|
|
*
|
|
* In Linux, the total length is limited to MAX_RW_COUNT, there is
|
|
* no overflow possibility.
|
|
*/
|
|
tot_len = 0;
|
|
ret = -EINVAL;
|
|
for (seg = 0; seg < nr_segs; seg++) {
|
|
compat_uptr_t buf;
|
|
compat_ssize_t len;
|
|
|
|
if (__get_user(len, &uvector->iov_len) ||
|
|
__get_user(buf, &uvector->iov_base)) {
|
|
ret = -EFAULT;
|
|
goto out;
|
|
}
|
|
if (len < 0) /* size_t not fitting in compat_ssize_t .. */
|
|
goto out;
|
|
if (type >= 0 &&
|
|
!access_ok(vrfy_dir(type), compat_ptr(buf), len)) {
|
|
ret = -EFAULT;
|
|
goto out;
|
|
}
|
|
if (len > MAX_RW_COUNT - tot_len)
|
|
len = MAX_RW_COUNT - tot_len;
|
|
tot_len += len;
|
|
iov->iov_base = compat_ptr(buf);
|
|
iov->iov_len = (compat_size_t) len;
|
|
uvector++;
|
|
iov++;
|
|
}
|
|
ret = tot_len;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
struct compat_ncp_mount_data {
|
|
compat_int_t version;
|
|
compat_uint_t ncp_fd;
|
|
__compat_uid_t mounted_uid;
|
|
compat_pid_t wdog_pid;
|
|
unsigned char mounted_vol[NCP_VOLNAME_LEN + 1];
|
|
compat_uint_t time_out;
|
|
compat_uint_t retry_count;
|
|
compat_uint_t flags;
|
|
__compat_uid_t uid;
|
|
__compat_gid_t gid;
|
|
compat_mode_t file_mode;
|
|
compat_mode_t dir_mode;
|
|
};
|
|
|
|
struct compat_ncp_mount_data_v4 {
|
|
compat_int_t version;
|
|
compat_ulong_t flags;
|
|
compat_ulong_t mounted_uid;
|
|
compat_long_t wdog_pid;
|
|
compat_uint_t ncp_fd;
|
|
compat_uint_t time_out;
|
|
compat_uint_t retry_count;
|
|
compat_ulong_t uid;
|
|
compat_ulong_t gid;
|
|
compat_ulong_t file_mode;
|
|
compat_ulong_t dir_mode;
|
|
};
|
|
|
|
static void *do_ncp_super_data_conv(void *raw_data)
|
|
{
|
|
int version = *(unsigned int *)raw_data;
|
|
|
|
if (version == 3) {
|
|
struct compat_ncp_mount_data *c_n = raw_data;
|
|
struct ncp_mount_data *n = raw_data;
|
|
|
|
n->dir_mode = c_n->dir_mode;
|
|
n->file_mode = c_n->file_mode;
|
|
n->gid = c_n->gid;
|
|
n->uid = c_n->uid;
|
|
memmove (n->mounted_vol, c_n->mounted_vol, (sizeof (c_n->mounted_vol) + 3 * sizeof (unsigned int)));
|
|
n->wdog_pid = c_n->wdog_pid;
|
|
n->mounted_uid = c_n->mounted_uid;
|
|
} else if (version == 4) {
|
|
struct compat_ncp_mount_data_v4 *c_n = raw_data;
|
|
struct ncp_mount_data_v4 *n = raw_data;
|
|
|
|
n->dir_mode = c_n->dir_mode;
|
|
n->file_mode = c_n->file_mode;
|
|
n->gid = c_n->gid;
|
|
n->uid = c_n->uid;
|
|
n->retry_count = c_n->retry_count;
|
|
n->time_out = c_n->time_out;
|
|
n->ncp_fd = c_n->ncp_fd;
|
|
n->wdog_pid = c_n->wdog_pid;
|
|
n->mounted_uid = c_n->mounted_uid;
|
|
n->flags = c_n->flags;
|
|
} else if (version != 5) {
|
|
return NULL;
|
|
}
|
|
|
|
return raw_data;
|
|
}
|
|
|
|
|
|
struct compat_nfs_string {
|
|
compat_uint_t len;
|
|
compat_uptr_t data;
|
|
};
|
|
|
|
static inline void compat_nfs_string(struct nfs_string *dst,
|
|
struct compat_nfs_string *src)
|
|
{
|
|
dst->data = compat_ptr(src->data);
|
|
dst->len = src->len;
|
|
}
|
|
|
|
struct compat_nfs4_mount_data_v1 {
|
|
compat_int_t version;
|
|
compat_int_t flags;
|
|
compat_int_t rsize;
|
|
compat_int_t wsize;
|
|
compat_int_t timeo;
|
|
compat_int_t retrans;
|
|
compat_int_t acregmin;
|
|
compat_int_t acregmax;
|
|
compat_int_t acdirmin;
|
|
compat_int_t acdirmax;
|
|
struct compat_nfs_string client_addr;
|
|
struct compat_nfs_string mnt_path;
|
|
struct compat_nfs_string hostname;
|
|
compat_uint_t host_addrlen;
|
|
compat_uptr_t host_addr;
|
|
compat_int_t proto;
|
|
compat_int_t auth_flavourlen;
|
|
compat_uptr_t auth_flavours;
|
|
};
|
|
|
|
static int do_nfs4_super_data_conv(void *raw_data)
|
|
{
|
|
int version = *(compat_uint_t *) raw_data;
|
|
|
|
if (version == 1) {
|
|
struct compat_nfs4_mount_data_v1 *raw = raw_data;
|
|
struct nfs4_mount_data *real = raw_data;
|
|
|
|
/* copy the fields backwards */
|
|
real->auth_flavours = compat_ptr(raw->auth_flavours);
|
|
real->auth_flavourlen = raw->auth_flavourlen;
|
|
real->proto = raw->proto;
|
|
real->host_addr = compat_ptr(raw->host_addr);
|
|
real->host_addrlen = raw->host_addrlen;
|
|
compat_nfs_string(&real->hostname, &raw->hostname);
|
|
compat_nfs_string(&real->mnt_path, &raw->mnt_path);
|
|
compat_nfs_string(&real->client_addr, &raw->client_addr);
|
|
real->acdirmax = raw->acdirmax;
|
|
real->acdirmin = raw->acdirmin;
|
|
real->acregmax = raw->acregmax;
|
|
real->acregmin = raw->acregmin;
|
|
real->retrans = raw->retrans;
|
|
real->timeo = raw->timeo;
|
|
real->wsize = raw->wsize;
|
|
real->rsize = raw->rsize;
|
|
real->flags = raw->flags;
|
|
real->version = raw->version;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define NCPFS_NAME "ncpfs"
|
|
#define NFS4_NAME "nfs4"
|
|
|
|
COMPAT_SYSCALL_DEFINE5(mount, const char __user *, dev_name,
|
|
const char __user *, dir_name,
|
|
const char __user *, type, compat_ulong_t, flags,
|
|
const void __user *, data)
|
|
{
|
|
char *kernel_type;
|
|
void *options;
|
|
char *kernel_dev;
|
|
int retval;
|
|
|
|
kernel_type = copy_mount_string(type);
|
|
retval = PTR_ERR(kernel_type);
|
|
if (IS_ERR(kernel_type))
|
|
goto out;
|
|
|
|
kernel_dev = copy_mount_string(dev_name);
|
|
retval = PTR_ERR(kernel_dev);
|
|
if (IS_ERR(kernel_dev))
|
|
goto out1;
|
|
|
|
options = copy_mount_options(data);
|
|
retval = PTR_ERR(options);
|
|
if (IS_ERR(options))
|
|
goto out2;
|
|
|
|
if (kernel_type && options) {
|
|
if (!strcmp(kernel_type, NCPFS_NAME)) {
|
|
do_ncp_super_data_conv(options);
|
|
} else if (!strcmp(kernel_type, NFS4_NAME)) {
|
|
retval = -EINVAL;
|
|
if (do_nfs4_super_data_conv(options))
|
|
goto out3;
|
|
}
|
|
}
|
|
|
|
retval = do_mount(kernel_dev, dir_name, kernel_type, flags, options);
|
|
|
|
out3:
|
|
kfree(options);
|
|
out2:
|
|
kfree(kernel_dev);
|
|
out1:
|
|
kfree(kernel_type);
|
|
out:
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Exactly like fs/open.c:sys_open(), except that it doesn't set the
|
|
* O_LARGEFILE flag.
|
|
*/
|
|
COMPAT_SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
|
|
{
|
|
return do_sys_open(AT_FDCWD, filename, flags, mode);
|
|
}
|
|
|
|
/*
|
|
* Exactly like fs/open.c:sys_openat(), except that it doesn't set the
|
|
* O_LARGEFILE flag.
|
|
*/
|
|
COMPAT_SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags, umode_t, mode)
|
|
{
|
|
return do_sys_open(dfd, filename, flags, mode);
|
|
}
|
|
|
|
#ifdef CONFIG_FHANDLE
|
|
/*
|
|
* Exactly like fs/open.c:sys_open_by_handle_at(), except that it
|
|
* doesn't set the O_LARGEFILE flag.
|
|
*/
|
|
COMPAT_SYSCALL_DEFINE3(open_by_handle_at, int, mountdirfd,
|
|
struct file_handle __user *, handle, int, flags)
|
|
{
|
|
return do_handle_open(mountdirfd, handle, flags);
|
|
}
|
|
#endif
|