forked from Minki/linux
da3f6f9b3e
This uses compat_alloc_userspace to remove the various hacks to allow do_sysctl to write to throuh oldlenp. The rest of our mature compat syscall helper facitilies are used as well to ensure we have a nice clean maintainable compat syscall that can be used on all architectures. The motiviation for a generic compat sysctl (besides the obvious hack removal) is to reduce the number of compat sysctl defintions out there so I can refactor the binary sysctl implementation. ppc already used the name compat_sys_sysctl so I remove the ppcs version here. Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Paul Mackerras <paulus@samba.org> Acked-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
229 lines
5.0 KiB
C
229 lines
5.0 KiB
C
#include <linux/stat.h>
|
|
#include <linux/sysctl.h>
|
|
#include "../fs/xfs/linux-2.6/xfs_sysctl.h"
|
|
#include <linux/sunrpc/debug.h>
|
|
#include <linux/string.h>
|
|
#include <net/ip_vs.h>
|
|
#include <linux/syscalls.h>
|
|
#include <linux/namei.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/nsproxy.h>
|
|
#include <linux/pid_namespace.h>
|
|
#include <linux/file.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/smp_lock.h>
|
|
|
|
#ifdef CONFIG_SYSCTL_SYSCALL
|
|
|
|
/* Perform the actual read/write of a sysctl table entry. */
|
|
static int do_sysctl_strategy(struct ctl_table_root *root,
|
|
struct ctl_table *table,
|
|
void __user *oldval, size_t __user *oldlenp,
|
|
void __user *newval, size_t newlen)
|
|
{
|
|
int op = 0, rc;
|
|
|
|
if (oldval)
|
|
op |= MAY_READ;
|
|
if (newval)
|
|
op |= MAY_WRITE;
|
|
if (sysctl_perm(root, table, op))
|
|
return -EPERM;
|
|
|
|
if (table->strategy) {
|
|
rc = table->strategy(table, oldval, oldlenp, newval, newlen);
|
|
if (rc < 0)
|
|
return rc;
|
|
if (rc > 0)
|
|
return 0;
|
|
}
|
|
|
|
/* If there is no strategy routine, or if the strategy returns
|
|
* zero, proceed with automatic r/w */
|
|
if (table->data && table->maxlen) {
|
|
rc = sysctl_data(table, oldval, oldlenp, newval, newlen);
|
|
if (rc < 0)
|
|
return rc;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int parse_table(const int *name, int nlen,
|
|
void __user *oldval, size_t __user *oldlenp,
|
|
void __user *newval, size_t newlen,
|
|
struct ctl_table_root *root,
|
|
struct ctl_table *table)
|
|
{
|
|
int n;
|
|
repeat:
|
|
if (!nlen)
|
|
return -ENOTDIR;
|
|
n = *name;
|
|
for ( ; table->ctl_name || table->procname; table++) {
|
|
if (!table->ctl_name)
|
|
continue;
|
|
if (n == table->ctl_name) {
|
|
int error;
|
|
if (table->child) {
|
|
if (sysctl_perm(root, table, MAY_EXEC))
|
|
return -EPERM;
|
|
name++;
|
|
nlen--;
|
|
table = table->child;
|
|
goto repeat;
|
|
}
|
|
error = do_sysctl_strategy(root, table,
|
|
oldval, oldlenp,
|
|
newval, newlen);
|
|
return error;
|
|
}
|
|
}
|
|
return -ENOTDIR;
|
|
}
|
|
|
|
static ssize_t binary_sysctl(const int *name, int nlen,
|
|
void __user *oldval, size_t __user *oldlenp,
|
|
void __user *newval, size_t newlen)
|
|
|
|
{
|
|
struct ctl_table_header *head;
|
|
ssize_t error = -ENOTDIR;
|
|
|
|
for (head = sysctl_head_next(NULL); head;
|
|
head = sysctl_head_next(head)) {
|
|
error = parse_table(name, nlen, oldval, oldlenp,
|
|
newval, newlen,
|
|
head->root, head->ctl_table);
|
|
if (error != -ENOTDIR) {
|
|
sysctl_head_finish(head);
|
|
break;
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
#else /* CONFIG_SYSCTL_SYSCALL */
|
|
|
|
static ssize_t binary_sysctl(const int *ctl_name, int nlen,
|
|
void __user *oldval, size_t __user *oldlenp,
|
|
void __user *newval, size_t newlen)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
#endif /* CONFIG_SYSCTL_SYSCALL */
|
|
|
|
static void deprecated_sysctl_warning(const int *name, int nlen)
|
|
{
|
|
static int msg_count;
|
|
int i;
|
|
|
|
/* Ignore accesses to kernel.version */
|
|
if ((nlen == 2) && (name[0] == CTL_KERN) && (name[1] == KERN_VERSION))
|
|
return;
|
|
|
|
if (msg_count < 5) {
|
|
msg_count++;
|
|
printk(KERN_INFO
|
|
"warning: process `%s' used the deprecated sysctl "
|
|
"system call with ", current->comm);
|
|
for (i = 0; i < nlen; i++)
|
|
printk("%d.", name[i]);
|
|
printk("\n");
|
|
}
|
|
return;
|
|
}
|
|
|
|
int do_sysctl(int __user *args_name, int nlen,
|
|
void __user *oldval, size_t __user *oldlenp,
|
|
void __user *newval, size_t newlen)
|
|
{
|
|
int name[CTL_MAXNAME];
|
|
size_t oldlen = 0;
|
|
int i;
|
|
|
|
if (nlen <= 0 || nlen >= CTL_MAXNAME)
|
|
return -ENOTDIR;
|
|
if (oldval && !oldlenp)
|
|
return -EFAULT;
|
|
if (oldlenp && get_user(oldlen, oldlenp))
|
|
return -EFAULT;
|
|
|
|
/* Read in the sysctl name for simplicity */
|
|
for (i = 0; i < nlen; i++)
|
|
if (get_user(name[i], args_name + i))
|
|
return -EFAULT;
|
|
|
|
deprecated_sysctl_warning(name, nlen);
|
|
|
|
return binary_sysctl(name, nlen, oldval, oldlenp, newval, newlen);
|
|
}
|
|
|
|
|
|
SYSCALL_DEFINE1(sysctl, struct __sysctl_args __user *, args)
|
|
{
|
|
struct __sysctl_args tmp;
|
|
int error;
|
|
|
|
if (copy_from_user(&tmp, args, sizeof(tmp)))
|
|
return -EFAULT;
|
|
|
|
lock_kernel();
|
|
error = do_sysctl(tmp.name, tmp.nlen, tmp.oldval, tmp.oldlenp,
|
|
tmp.newval, tmp.newlen);
|
|
unlock_kernel();
|
|
|
|
return error;
|
|
}
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
#include <asm/compat.h>
|
|
|
|
struct compat_sysctl_args {
|
|
compat_uptr_t name;
|
|
int nlen;
|
|
compat_uptr_t oldval;
|
|
compat_uptr_t oldlenp;
|
|
compat_uptr_t newval;
|
|
compat_size_t newlen;
|
|
compat_ulong_t __unused[4];
|
|
};
|
|
|
|
asmlinkage long compat_sys_sysctl(struct compat_sysctl_args __user *args)
|
|
{
|
|
struct compat_sysctl_args tmp;
|
|
compat_size_t __user *compat_oldlenp;
|
|
size_t __user *oldlenp = NULL;
|
|
size_t oldlen = 0;
|
|
ssize_t result;
|
|
|
|
if (copy_from_user(&tmp, args, sizeof(tmp)))
|
|
return -EFAULT;
|
|
|
|
compat_oldlenp = compat_ptr(tmp.oldlenp);
|
|
if (compat_oldlenp) {
|
|
oldlenp = compat_alloc_user_space(sizeof(*compat_oldlenp));
|
|
|
|
if (get_user(oldlen, compat_oldlenp) ||
|
|
put_user(oldlen, oldlenp))
|
|
return -EFAULT;
|
|
}
|
|
|
|
lock_kernel();
|
|
result = do_sysctl(compat_ptr(tmp.name), tmp.nlen,
|
|
compat_ptr(tmp.oldval), oldlenp,
|
|
compat_ptr(tmp.newval), tmp.newlen);
|
|
unlock_kernel();
|
|
|
|
if (oldlenp && !result) {
|
|
if (get_user(oldlen, oldlenp) ||
|
|
put_user(oldlen, compat_oldlenp))
|
|
return -EFAULT;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif /* CONFIG_COMPAT */
|