1466 lines · 36.7 KB · C
RawDownload
/* *  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;} /* * Not all architectures have sys_utime, so implement this in terms * of sys_utimes. */asmlinkage long compat_sys_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);} asmlinkage long compat_sys_utimensat(unsigned int dfd, const char __user *filename, struct compat_timespec __user *t, int flags){	struct timespec tv[2]; 	if  (t) {		if (get_compat_timespec(&tv[0], &t[0]) ||		    get_compat_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);} asmlinkage long compat_sys_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);} asmlinkage long compat_sys_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;} asmlinkage long compat_sys_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);} asmlinkage long compat_sys_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_STAT64asmlinkage long compat_sys_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 asmlinkage long compat_sys_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;} /* * The following statfs calls are copies of code from fs/statfs.c and * should be checked against those from time to time */asmlinkage long compat_sys_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;} asmlinkage long compat_sys_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;} asmlinkage long compat_sys_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;} asmlinkage long compat_sys_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. */asmlinkage long compat_sys_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;} 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_FLOCK64static 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_FLOCK64static 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 asmlinkage long compat_sys_fcntl64(unsigned int fd, unsigned int cmd,		unsigned long arg){	mm_segment_t old_fs;	struct flock f;	long ret; 	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:		ret = get_compat_flock64(&f, compat_ptr(arg));		if (ret != 0)			break;		old_fs = get_fs();		set_fs(KERNEL_DS);		ret = sys_fcntl(fd, (cmd == F_GETLK64) ? F_GETLK :				((cmd == F_SETLK64) ? F_SETLK : F_SETLKW),				(unsigned long)&f);		set_fs(old_fs);		if (cmd == F_GETLK64 && 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;} asmlinkage long compat_sys_fcntl(unsigned int fd, unsigned int cmd,		unsigned long arg){	if ((cmd == F_GETLK64) || (cmd == F_SETLK64) || (cmd == F_SETLKW64))		return -EINVAL;	return compat_sys_fcntl64(fd, cmd, arg);} asmlinkage longcompat_sys_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;} asmlinkage longcompat_sys_io_getevents(aio_context_t ctx_id,

Lines 1–500 of 1466.