Created
January 26, 2019 19:55
-
-
Save lowlyocean/c4edf733e7d65e270dfd88dd908bc8d3 to your computer and use it in GitHub Desktop.
linux/kernel/sys.c compared: v2.6.26 and v3.2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/kernel/sys.c b/kernel/sys.c | |
| index 14e97282eb6c..481611fbd079 100644 | |
| --- a/kernel/sys.c | |
| +++ b/kernel/sys.c | |
| @@ -4,16 +4,16 @@ | |
| * Copyright (C) 1991, 1992 Linus Torvalds | |
| */ | |
| -#include <linux/module.h> | |
| +#include <linux/export.h> | |
| #include <linux/mm.h> | |
| #include <linux/utsname.h> | |
| #include <linux/mman.h> | |
| -#include <linux/smp_lock.h> | |
| -#include <linux/notifier.h> | |
| #include <linux/reboot.h> | |
| #include <linux/prctl.h> | |
| #include <linux/highuid.h> | |
| #include <linux/fs.h> | |
| +#include <linux/kmod.h> | |
| +#include <linux/perf_event.h> | |
| #include <linux/resource.h> | |
| #include <linux/kernel.h> | |
| #include <linux/kexec.h> | |
| @@ -33,12 +33,23 @@ | |
| #include <linux/task_io_accounting_ops.h> | |
| #include <linux/seccomp.h> | |
| #include <linux/cpu.h> | |
| +#include <linux/personality.h> | |
| +#include <linux/ptrace.h> | |
| +#include <linux/fs_struct.h> | |
| +#include <linux/gfp.h> | |
| +#include <linux/syscore_ops.h> | |
| +#include <linux/version.h> | |
| +#include <linux/ctype.h> | |
| #include <linux/compat.h> | |
| #include <linux/syscalls.h> | |
| #include <linux/kprobes.h> | |
| #include <linux/user_namespace.h> | |
| +#include <linux/kmsg_dump.h> | |
| +/* Move somewhere else to avoid recompiling? */ | |
| +#include <generated/utsrelease.h> | |
| + | |
| #include <asm/uaccess.h> | |
| #include <asm/io.h> | |
| #include <asm/unistd.h> | |
| @@ -112,12 +123,34 @@ EXPORT_SYMBOL(cad_pid); | |
| void (*pm_power_off_prepare)(void); | |
| +/* | |
| + * Returns true if current's euid is same as p's uid or euid, | |
| + * or has CAP_SYS_NICE to p's user_ns. | |
| + * | |
| + * Called with rcu_read_lock, creds are safe | |
| + */ | |
| +static bool set_one_prio_perm(struct task_struct *p) | |
| +{ | |
| + const struct cred *cred = current_cred(), *pcred = __task_cred(p); | |
| + | |
| + if (pcred->user->user_ns == cred->user->user_ns && | |
| + (pcred->uid == cred->euid || | |
| + pcred->euid == cred->euid)) | |
| + return true; | |
| + if (ns_capable(pcred->user->user_ns, CAP_SYS_NICE)) | |
| + return true; | |
| + return false; | |
| +} | |
| + | |
| +/* | |
| + * set the priority of a task | |
| + * - the caller must hold the RCU read lock | |
| + */ | |
| static int set_one_prio(struct task_struct *p, int niceval, int error) | |
| { | |
| int no_nice; | |
| - if (p->uid != current->euid && | |
| - p->euid != current->euid && !capable(CAP_SYS_NICE)) { | |
| + if (!set_one_prio_perm(p)) { | |
| error = -EPERM; | |
| goto out; | |
| } | |
| @@ -137,10 +170,11 @@ static int set_one_prio(struct task_struct *p, int niceval, int error) | |
| return error; | |
| } | |
| -asmlinkage long sys_setpriority(int which, int who, int niceval) | |
| +SYSCALL_DEFINE3(setpriority, int, which, int, who, int, niceval) | |
| { | |
| struct task_struct *g, *p; | |
| struct user_struct *user; | |
| + const struct cred *cred = current_cred(); | |
| int error = -EINVAL; | |
| struct pid *pgrp; | |
| @@ -154,6 +188,7 @@ asmlinkage long sys_setpriority(int which, int who, int niceval) | |
| if (niceval > 19) | |
| niceval = 19; | |
| + rcu_read_lock(); | |
| read_lock(&tasklist_lock); | |
| switch (which) { | |
| case PRIO_PROCESS: | |
| @@ -169,28 +204,29 @@ asmlinkage long sys_setpriority(int which, int who, int niceval) | |
| pgrp = find_vpid(who); | |
| else | |
| pgrp = task_pgrp(current); | |
| - do_each_pid_task(pgrp, PIDTYPE_PGID, p) { | |
| + do_each_pid_thread(pgrp, PIDTYPE_PGID, p) { | |
| error = set_one_prio(p, niceval, error); | |
| - } while_each_pid_task(pgrp, PIDTYPE_PGID, p); | |
| + } while_each_pid_thread(pgrp, PIDTYPE_PGID, p); | |
| break; | |
| case PRIO_USER: | |
| - user = current->user; | |
| + user = (struct user_struct *) cred->user; | |
| if (!who) | |
| - who = current->uid; | |
| - else | |
| - if ((who != current->uid) && !(user = find_user(who))) | |
| - goto out_unlock; /* No processes for this user */ | |
| + who = cred->uid; | |
| + else if ((who != cred->uid) && | |
| + !(user = find_user(who))) | |
| + goto out_unlock; /* No processes for this user */ | |
| - do_each_thread(g, p) | |
| - if (p->uid == who) | |
| + do_each_thread(g, p) { | |
| + if (__task_cred(p)->uid == who) | |
| error = set_one_prio(p, niceval, error); | |
| - while_each_thread(g, p); | |
| - if (who != current->uid) | |
| + } while_each_thread(g, p); | |
| + if (who != cred->uid) | |
| free_uid(user); /* For find_user() */ | |
| break; | |
| } | |
| out_unlock: | |
| read_unlock(&tasklist_lock); | |
| + rcu_read_unlock(); | |
| out: | |
| return error; | |
| } | |
| @@ -201,16 +237,18 @@ asmlinkage long sys_setpriority(int which, int who, int niceval) | |
| * has been offset by 20 (ie it returns 40..1 instead of -20..19) | |
| * to stay compatible. | |
| */ | |
| -asmlinkage long sys_getpriority(int which, int who) | |
| +SYSCALL_DEFINE2(getpriority, int, which, int, who) | |
| { | |
| struct task_struct *g, *p; | |
| struct user_struct *user; | |
| + const struct cred *cred = current_cred(); | |
| long niceval, retval = -ESRCH; | |
| struct pid *pgrp; | |
| if (which > PRIO_USER || which < PRIO_PROCESS) | |
| return -EINVAL; | |
| + rcu_read_lock(); | |
| read_lock(&tasklist_lock); | |
| switch (which) { | |
| case PRIO_PROCESS: | |
| @@ -229,33 +267,34 @@ asmlinkage long sys_getpriority(int which, int who) | |
| pgrp = find_vpid(who); | |
| else | |
| pgrp = task_pgrp(current); | |
| - do_each_pid_task(pgrp, PIDTYPE_PGID, p) { | |
| + do_each_pid_thread(pgrp, PIDTYPE_PGID, p) { | |
| niceval = 20 - task_nice(p); | |
| if (niceval > retval) | |
| retval = niceval; | |
| - } while_each_pid_task(pgrp, PIDTYPE_PGID, p); | |
| + } while_each_pid_thread(pgrp, PIDTYPE_PGID, p); | |
| break; | |
| case PRIO_USER: | |
| - user = current->user; | |
| + user = (struct user_struct *) cred->user; | |
| if (!who) | |
| - who = current->uid; | |
| - else | |
| - if ((who != current->uid) && !(user = find_user(who))) | |
| - goto out_unlock; /* No processes for this user */ | |
| + who = cred->uid; | |
| + else if ((who != cred->uid) && | |
| + !(user = find_user(who))) | |
| + goto out_unlock; /* No processes for this user */ | |
| - do_each_thread(g, p) | |
| - if (p->uid == who) { | |
| + do_each_thread(g, p) { | |
| + if (__task_cred(p)->uid == who) { | |
| niceval = 20 - task_nice(p); | |
| if (niceval > retval) | |
| retval = niceval; | |
| } | |
| - while_each_thread(g, p); | |
| - if (who != current->uid) | |
| + } while_each_thread(g, p); | |
| + if (who != cred->uid) | |
| free_uid(user); /* for find_user() */ | |
| break; | |
| } | |
| out_unlock: | |
| read_unlock(&tasklist_lock); | |
| + rcu_read_unlock(); | |
| return retval; | |
| } | |
| @@ -270,17 +309,50 @@ asmlinkage long sys_getpriority(int which, int who) | |
| */ | |
| void emergency_restart(void) | |
| { | |
| + kmsg_dump(KMSG_DUMP_EMERG); | |
| machine_emergency_restart(); | |
| } | |
| EXPORT_SYMBOL_GPL(emergency_restart); | |
| -static void kernel_restart_prepare(char *cmd) | |
| +void kernel_restart_prepare(char *cmd) | |
| { | |
| blocking_notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd); | |
| system_state = SYSTEM_RESTART; | |
| + usermodehelper_disable(); | |
| device_shutdown(); | |
| - sysdev_shutdown(); | |
| + syscore_shutdown(); | |
| +} | |
| + | |
| +/** | |
| + * register_reboot_notifier - Register function to be called at reboot time | |
| + * @nb: Info about notifier function to be called | |
| + * | |
| + * Registers a function with the list of functions | |
| + * to be called at reboot time. | |
| + * | |
| + * Currently always returns zero, as blocking_notifier_chain_register() | |
| + * always returns zero. | |
| + */ | |
| +int register_reboot_notifier(struct notifier_block *nb) | |
| +{ | |
| + return blocking_notifier_chain_register(&reboot_notifier_list, nb); | |
| } | |
| +EXPORT_SYMBOL(register_reboot_notifier); | |
| + | |
| +/** | |
| + * unregister_reboot_notifier - Unregister previously registered reboot notifier | |
| + * @nb: Hook to be unregistered | |
| + * | |
| + * Unregisters a previously registered reboot | |
| + * notifier function. | |
| + * | |
| + * Returns zero on success, or %-ENOENT on failure. | |
| + */ | |
| +int unregister_reboot_notifier(struct notifier_block *nb) | |
| +{ | |
| + return blocking_notifier_chain_unregister(&reboot_notifier_list, nb); | |
| +} | |
| +EXPORT_SYMBOL(unregister_reboot_notifier); | |
| /** | |
| * kernel_restart - reboot the system | |
| @@ -297,35 +369,17 @@ void kernel_restart(char *cmd) | |
| printk(KERN_EMERG "Restarting system.\n"); | |
| else | |
| printk(KERN_EMERG "Restarting system with command '%s'.\n", cmd); | |
| + kmsg_dump(KMSG_DUMP_RESTART); | |
| machine_restart(cmd); | |
| } | |
| EXPORT_SYMBOL_GPL(kernel_restart); | |
| -/** | |
| - * kernel_kexec - reboot the system | |
| - * | |
| - * Move into place and start executing a preloaded standalone | |
| - * executable. If nothing was preloaded return an error. | |
| - */ | |
| -static void kernel_kexec(void) | |
| -{ | |
| -#ifdef CONFIG_KEXEC | |
| - struct kimage *image; | |
| - image = xchg(&kexec_image, NULL); | |
| - if (!image) | |
| - return; | |
| - kernel_restart_prepare(NULL); | |
| - printk(KERN_EMERG "Starting new kernel\n"); | |
| - machine_shutdown(); | |
| - machine_kexec(image); | |
| -#endif | |
| -} | |
| - | |
| static void kernel_shutdown_prepare(enum system_states state) | |
| { | |
| blocking_notifier_call_chain(&reboot_notifier_list, | |
| (state == SYSTEM_HALT)?SYS_HALT:SYS_POWER_OFF, NULL); | |
| system_state = state; | |
| + usermodehelper_disable(); | |
| device_shutdown(); | |
| } | |
| /** | |
| @@ -336,8 +390,9 @@ static void kernel_shutdown_prepare(enum system_states state) | |
| void kernel_halt(void) | |
| { | |
| kernel_shutdown_prepare(SYSTEM_HALT); | |
| - sysdev_shutdown(); | |
| + syscore_shutdown(); | |
| printk(KERN_EMERG "System halted.\n"); | |
| + kmsg_dump(KMSG_DUMP_HALT); | |
| machine_halt(); | |
| } | |
| @@ -354,11 +409,15 @@ void kernel_power_off(void) | |
| if (pm_power_off_prepare) | |
| pm_power_off_prepare(); | |
| disable_nonboot_cpus(); | |
| - sysdev_shutdown(); | |
| + syscore_shutdown(); | |
| printk(KERN_EMERG "Power down.\n"); | |
| + kmsg_dump(KMSG_DUMP_POWEROFF); | |
| machine_power_off(); | |
| } | |
| EXPORT_SYMBOL_GPL(kernel_power_off); | |
| + | |
| +static DEFINE_MUTEX(reboot_mutex); | |
| + | |
| /* | |
| * Reboot system call: for obvious reasons only root may call it, | |
| * and even root needs to set up some magic numbers in the registers | |
| @@ -367,9 +426,11 @@ EXPORT_SYMBOL_GPL(kernel_power_off); | |
| * | |
| * reboot doesn't sync: do that yourself before calling this. | |
| */ | |
| -asmlinkage long sys_reboot(int magic1, int magic2, unsigned int cmd, void __user * arg) | |
| +SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd, | |
| + void __user *, arg) | |
| { | |
| char buffer[256]; | |
| + int ret = 0; | |
| /* We only trust the superuser with rebooting the system. */ | |
| if (!capable(CAP_SYS_BOOT)) | |
| @@ -389,7 +450,7 @@ asmlinkage long sys_reboot(int magic1, int magic2, unsigned int cmd, void __user | |
| if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off) | |
| cmd = LINUX_REBOOT_CMD_HALT; | |
| - lock_kernel(); | |
| + mutex_lock(&reboot_mutex); | |
| switch (cmd) { | |
| case LINUX_REBOOT_CMD_RESTART: | |
| kernel_restart(NULL); | |
| @@ -405,46 +466,42 @@ asmlinkage long sys_reboot(int magic1, int magic2, unsigned int cmd, void __user | |
| case LINUX_REBOOT_CMD_HALT: | |
| kernel_halt(); | |
| - unlock_kernel(); | |
| do_exit(0); | |
| - break; | |
| + panic("cannot halt"); | |
| case LINUX_REBOOT_CMD_POWER_OFF: | |
| kernel_power_off(); | |
| - unlock_kernel(); | |
| do_exit(0); | |
| break; | |
| case LINUX_REBOOT_CMD_RESTART2: | |
| if (strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1) < 0) { | |
| - unlock_kernel(); | |
| - return -EFAULT; | |
| + ret = -EFAULT; | |
| + break; | |
| } | |
| buffer[sizeof(buffer) - 1] = '\0'; | |
| kernel_restart(buffer); | |
| break; | |
| +#ifdef CONFIG_KEXEC | |
| case LINUX_REBOOT_CMD_KEXEC: | |
| - kernel_kexec(); | |
| - unlock_kernel(); | |
| - return -EINVAL; | |
| + ret = kernel_kexec(); | |
| + break; | |
| +#endif | |
| #ifdef CONFIG_HIBERNATION | |
| case LINUX_REBOOT_CMD_SW_SUSPEND: | |
| - { | |
| - int ret = hibernate(); | |
| - unlock_kernel(); | |
| - return ret; | |
| - } | |
| + ret = hibernate(); | |
| + break; | |
| #endif | |
| default: | |
| - unlock_kernel(); | |
| - return -EINVAL; | |
| + ret = -EINVAL; | |
| + break; | |
| } | |
| - unlock_kernel(); | |
| - return 0; | |
| + mutex_unlock(&reboot_mutex); | |
| + return ret; | |
| } | |
| static void deferred_cad(struct work_struct *dummy) | |
| @@ -485,48 +542,46 @@ void ctrl_alt_del(void) | |
| * SMP: There are not races, the GIDs are checked only by filesystem | |
| * operations (as far as semantic preservation is concerned). | |
| */ | |
| -asmlinkage long sys_setregid(gid_t rgid, gid_t egid) | |
| +SYSCALL_DEFINE2(setregid, gid_t, rgid, gid_t, egid) | |
| { | |
| - int old_rgid = current->gid; | |
| - int old_egid = current->egid; | |
| - int new_rgid = old_rgid; | |
| - int new_egid = old_egid; | |
| + const struct cred *old; | |
| + struct cred *new; | |
| int retval; | |
| - retval = security_task_setgid(rgid, egid, (gid_t)-1, LSM_SETID_RE); | |
| - if (retval) | |
| - return retval; | |
| + new = prepare_creds(); | |
| + if (!new) | |
| + return -ENOMEM; | |
| + old = current_cred(); | |
| + retval = -EPERM; | |
| if (rgid != (gid_t) -1) { | |
| - if ((old_rgid == rgid) || | |
| - (current->egid==rgid) || | |
| - capable(CAP_SETGID)) | |
| - new_rgid = rgid; | |
| + if (old->gid == rgid || | |
| + old->egid == rgid || | |
| + nsown_capable(CAP_SETGID)) | |
| + new->gid = rgid; | |
| else | |
| - return -EPERM; | |
| + goto error; | |
| } | |
| if (egid != (gid_t) -1) { | |
| - if ((old_rgid == egid) || | |
| - (current->egid == egid) || | |
| - (current->sgid == egid) || | |
| - capable(CAP_SETGID)) | |
| - new_egid = egid; | |
| + if (old->gid == egid || | |
| + old->egid == egid || | |
| + old->sgid == egid || | |
| + nsown_capable(CAP_SETGID)) | |
| + new->egid = egid; | |
| else | |
| - return -EPERM; | |
| - } | |
| - if (new_egid != old_egid) { | |
| - set_dumpable(current->mm, suid_dumpable); | |
| - smp_wmb(); | |
| + goto error; | |
| } | |
| + | |
| if (rgid != (gid_t) -1 || | |
| - (egid != (gid_t) -1 && egid != old_rgid)) | |
| - current->sgid = new_egid; | |
| - current->fsgid = new_egid; | |
| - current->egid = new_egid; | |
| - current->gid = new_rgid; | |
| - key_fsgid_changed(current); | |
| - proc_id_connector(current, PROC_EVENT_GID); | |
| - return 0; | |
| + (egid != (gid_t) -1 && egid != old->gid)) | |
| + new->sgid = new->egid; | |
| + new->fsgid = new->egid; | |
| + | |
| + return commit_creds(new); | |
| + | |
| +error: | |
| + abort_creds(new); | |
| + return retval; | |
| } | |
| /* | |
| @@ -534,58 +589,58 @@ asmlinkage long sys_setregid(gid_t rgid, gid_t egid) | |
| * | |
| * SMP: Same implicit races as above. | |
| */ | |
| -asmlinkage long sys_setgid(gid_t gid) | |
| +SYSCALL_DEFINE1(setgid, gid_t, gid) | |
| { | |
| - int old_egid = current->egid; | |
| + const struct cred *old; | |
| + struct cred *new; | |
| int retval; | |
| - retval = security_task_setgid(gid, (gid_t)-1, (gid_t)-1, LSM_SETID_ID); | |
| - if (retval) | |
| - return retval; | |
| + new = prepare_creds(); | |
| + if (!new) | |
| + return -ENOMEM; | |
| + old = current_cred(); | |
| - if (capable(CAP_SETGID)) { | |
| - if (old_egid != gid) { | |
| - set_dumpable(current->mm, suid_dumpable); | |
| - smp_wmb(); | |
| - } | |
| - current->gid = current->egid = current->sgid = current->fsgid = gid; | |
| - } else if ((gid == current->gid) || (gid == current->sgid)) { | |
| - if (old_egid != gid) { | |
| - set_dumpable(current->mm, suid_dumpable); | |
| - smp_wmb(); | |
| - } | |
| - current->egid = current->fsgid = gid; | |
| - } | |
| + retval = -EPERM; | |
| + if (nsown_capable(CAP_SETGID)) | |
| + new->gid = new->egid = new->sgid = new->fsgid = gid; | |
| + else if (gid == old->gid || gid == old->sgid) | |
| + new->egid = new->fsgid = gid; | |
| else | |
| - return -EPERM; | |
| + goto error; | |
| - key_fsgid_changed(current); | |
| - proc_id_connector(current, PROC_EVENT_GID); | |
| - return 0; | |
| + return commit_creds(new); | |
| + | |
| +error: | |
| + abort_creds(new); | |
| + return retval; | |
| } | |
| - | |
| -static int set_user(uid_t new_ruid, int dumpclear) | |
| + | |
| +/* | |
| + * change the user struct in a credentials set to match the new UID | |
| + */ | |
| +static int set_user(struct cred *new) | |
| { | |
| struct user_struct *new_user; | |
| - new_user = alloc_uid(current->nsproxy->user_ns, new_ruid); | |
| + new_user = alloc_uid(current_user_ns(), new->uid); | |
| if (!new_user) | |
| return -EAGAIN; | |
| - if (atomic_read(&new_user->processes) >= | |
| - current->signal->rlim[RLIMIT_NPROC].rlim_cur && | |
| - new_user != current->nsproxy->user_ns->root_user) { | |
| - free_uid(new_user); | |
| - return -EAGAIN; | |
| - } | |
| - | |
| - switch_uid(new_user); | |
| + /* | |
| + * We don't fail in case of NPROC limit excess here because too many | |
| + * poorly written programs don't check set*uid() return code, assuming | |
| + * it never fails if called by root. We may still enforce NPROC limit | |
| + * for programs doing set*uid()+execve() by harmlessly deferring the | |
| + * failure to the execve() stage. | |
| + */ | |
| + if (atomic_read(&new_user->processes) >= rlimit(RLIMIT_NPROC) && | |
| + new_user != INIT_USER) | |
| + current->flags |= PF_NPROC_EXCEEDED; | |
| + else | |
| + current->flags &= ~PF_NPROC_EXCEEDED; | |
| - if (dumpclear) { | |
| - set_dumpable(current->mm, suid_dumpable); | |
| - smp_wmb(); | |
| - } | |
| - current->uid = new_ruid; | |
| + free_uid(new->user); | |
| + new->user = new_user; | |
| return 0; | |
| } | |
| @@ -604,56 +659,55 @@ static int set_user(uid_t new_ruid, int dumpclear) | |
| * 100% compatible with BSD. A program which uses just setuid() will be | |
| * 100% compatible with POSIX with saved IDs. | |
| */ | |
| -asmlinkage long sys_setreuid(uid_t ruid, uid_t euid) | |
| +SYSCALL_DEFINE2(setreuid, uid_t, ruid, uid_t, euid) | |
| { | |
| - int old_ruid, old_euid, old_suid, new_ruid, new_euid; | |
| + const struct cred *old; | |
| + struct cred *new; | |
| int retval; | |
| - retval = security_task_setuid(ruid, euid, (uid_t)-1, LSM_SETID_RE); | |
| - if (retval) | |
| - return retval; | |
| - | |
| - new_ruid = old_ruid = current->uid; | |
| - new_euid = old_euid = current->euid; | |
| - old_suid = current->suid; | |
| + new = prepare_creds(); | |
| + if (!new) | |
| + return -ENOMEM; | |
| + old = current_cred(); | |
| + retval = -EPERM; | |
| if (ruid != (uid_t) -1) { | |
| - new_ruid = ruid; | |
| - if ((old_ruid != ruid) && | |
| - (current->euid != ruid) && | |
| - !capable(CAP_SETUID)) | |
| - return -EPERM; | |
| + new->uid = ruid; | |
| + if (old->uid != ruid && | |
| + old->euid != ruid && | |
| + !nsown_capable(CAP_SETUID)) | |
| + goto error; | |
| } | |
| if (euid != (uid_t) -1) { | |
| - new_euid = euid; | |
| - if ((old_ruid != euid) && | |
| - (current->euid != euid) && | |
| - (current->suid != euid) && | |
| - !capable(CAP_SETUID)) | |
| - return -EPERM; | |
| + new->euid = euid; | |
| + if (old->uid != euid && | |
| + old->euid != euid && | |
| + old->suid != euid && | |
| + !nsown_capable(CAP_SETUID)) | |
| + goto error; | |
| } | |
| - if (new_ruid != old_ruid && set_user(new_ruid, new_euid != old_euid) < 0) | |
| - return -EAGAIN; | |
| - | |
| - if (new_euid != old_euid) { | |
| - set_dumpable(current->mm, suid_dumpable); | |
| - smp_wmb(); | |
| + if (new->uid != old->uid) { | |
| + retval = set_user(new); | |
| + if (retval < 0) | |
| + goto error; | |
| } | |
| - current->fsuid = current->euid = new_euid; | |
| if (ruid != (uid_t) -1 || | |
| - (euid != (uid_t) -1 && euid != old_ruid)) | |
| - current->suid = current->euid; | |
| - current->fsuid = current->euid; | |
| + (euid != (uid_t) -1 && euid != old->uid)) | |
| + new->suid = new->euid; | |
| + new->fsuid = new->euid; | |
| - key_fsuid_changed(current); | |
| - proc_id_connector(current, PROC_EVENT_UID); | |
| - | |
| - return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_RE); | |
| -} | |
| + retval = security_task_fix_setuid(new, old, LSM_SETID_RE); | |
| + if (retval < 0) | |
| + goto error; | |
| + return commit_creds(new); | |
| +error: | |
| + abort_creds(new); | |
| + return retval; | |
| +} | |
| /* | |
| * setuid() is implemented like SysV with SAVED_IDS | |
| @@ -666,38 +720,40 @@ asmlinkage long sys_setreuid(uid_t ruid, uid_t euid) | |
| * will allow a root program to temporarily drop privileges and be able to | |
| * regain them by swapping the real and effective uid. | |
| */ | |
| -asmlinkage long sys_setuid(uid_t uid) | |
| +SYSCALL_DEFINE1(setuid, uid_t, uid) | |
| { | |
| - int old_euid = current->euid; | |
| - int old_ruid, old_suid, new_suid; | |
| + const struct cred *old; | |
| + struct cred *new; | |
| int retval; | |
| - retval = security_task_setuid(uid, (uid_t)-1, (uid_t)-1, LSM_SETID_ID); | |
| - if (retval) | |
| - return retval; | |
| + new = prepare_creds(); | |
| + if (!new) | |
| + return -ENOMEM; | |
| + old = current_cred(); | |
| + | |
| + retval = -EPERM; | |
| + if (nsown_capable(CAP_SETUID)) { | |
| + new->suid = new->uid = uid; | |
| + if (uid != old->uid) { | |
| + retval = set_user(new); | |
| + if (retval < 0) | |
| + goto error; | |
| + } | |
| + } else if (uid != old->uid && uid != new->suid) { | |
| + goto error; | |
| + } | |
| - old_ruid = current->uid; | |
| - old_suid = current->suid; | |
| - new_suid = old_suid; | |
| - | |
| - if (capable(CAP_SETUID)) { | |
| - if (uid != old_ruid && set_user(uid, old_euid != uid) < 0) | |
| - return -EAGAIN; | |
| - new_suid = uid; | |
| - } else if ((uid != current->uid) && (uid != new_suid)) | |
| - return -EPERM; | |
| + new->fsuid = new->euid = uid; | |
| - if (old_euid != uid) { | |
| - set_dumpable(current->mm, suid_dumpable); | |
| - smp_wmb(); | |
| - } | |
| - current->fsuid = current->euid = uid; | |
| - current->suid = new_suid; | |
| + retval = security_task_fix_setuid(new, old, LSM_SETID_ID); | |
| + if (retval < 0) | |
| + goto error; | |
| - key_fsuid_changed(current); | |
| - proc_id_connector(current, PROC_EVENT_UID); | |
| + return commit_creds(new); | |
| - return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_ID); | |
| +error: | |
| + abort_creds(new); | |
| + return retval; | |
| } | |
| @@ -705,56 +761,64 @@ asmlinkage long sys_setuid(uid_t uid) | |
| * This function implements a generic ability to update ruid, euid, | |
| * and suid. This allows you to implement the 4.4 compatible seteuid(). | |
| */ | |
| -asmlinkage long sys_setresuid(uid_t ruid, uid_t euid, uid_t suid) | |
| +SYSCALL_DEFINE3(setresuid, uid_t, ruid, uid_t, euid, uid_t, suid) | |
| { | |
| - int old_ruid = current->uid; | |
| - int old_euid = current->euid; | |
| - int old_suid = current->suid; | |
| + const struct cred *old; | |
| + struct cred *new; | |
| int retval; | |
| - retval = security_task_setuid(ruid, euid, suid, LSM_SETID_RES); | |
| - if (retval) | |
| - return retval; | |
| + new = prepare_creds(); | |
| + if (!new) | |
| + return -ENOMEM; | |
| - if (!capable(CAP_SETUID)) { | |
| - if ((ruid != (uid_t) -1) && (ruid != current->uid) && | |
| - (ruid != current->euid) && (ruid != current->suid)) | |
| - return -EPERM; | |
| - if ((euid != (uid_t) -1) && (euid != current->uid) && | |
| - (euid != current->euid) && (euid != current->suid)) | |
| - return -EPERM; | |
| - if ((suid != (uid_t) -1) && (suid != current->uid) && | |
| - (suid != current->euid) && (suid != current->suid)) | |
| - return -EPERM; | |
| + old = current_cred(); | |
| + | |
| + retval = -EPERM; | |
| + if (!nsown_capable(CAP_SETUID)) { | |
| + if (ruid != (uid_t) -1 && ruid != old->uid && | |
| + ruid != old->euid && ruid != old->suid) | |
| + goto error; | |
| + if (euid != (uid_t) -1 && euid != old->uid && | |
| + euid != old->euid && euid != old->suid) | |
| + goto error; | |
| + if (suid != (uid_t) -1 && suid != old->uid && | |
| + suid != old->euid && suid != old->suid) | |
| + goto error; | |
| } | |
| + | |
| if (ruid != (uid_t) -1) { | |
| - if (ruid != current->uid && set_user(ruid, euid != current->euid) < 0) | |
| - return -EAGAIN; | |
| - } | |
| - if (euid != (uid_t) -1) { | |
| - if (euid != current->euid) { | |
| - set_dumpable(current->mm, suid_dumpable); | |
| - smp_wmb(); | |
| + new->uid = ruid; | |
| + if (ruid != old->uid) { | |
| + retval = set_user(new); | |
| + if (retval < 0) | |
| + goto error; | |
| } | |
| - current->euid = euid; | |
| } | |
| - current->fsuid = current->euid; | |
| + if (euid != (uid_t) -1) | |
| + new->euid = euid; | |
| if (suid != (uid_t) -1) | |
| - current->suid = suid; | |
| + new->suid = suid; | |
| + new->fsuid = new->euid; | |
| - key_fsuid_changed(current); | |
| - proc_id_connector(current, PROC_EVENT_UID); | |
| + retval = security_task_fix_setuid(new, old, LSM_SETID_RES); | |
| + if (retval < 0) | |
| + goto error; | |
| - return security_task_post_setuid(old_ruid, old_euid, old_suid, LSM_SETID_RES); | |
| + return commit_creds(new); | |
| + | |
| +error: | |
| + abort_creds(new); | |
| + return retval; | |
| } | |
| -asmlinkage long sys_getresuid(uid_t __user *ruid, uid_t __user *euid, uid_t __user *suid) | |
| +SYSCALL_DEFINE3(getresuid, uid_t __user *, ruid, uid_t __user *, euid, uid_t __user *, suid) | |
| { | |
| + const struct cred *cred = current_cred(); | |
| int retval; | |
| - if (!(retval = put_user(current->uid, ruid)) && | |
| - !(retval = put_user(current->euid, euid))) | |
| - retval = put_user(current->suid, suid); | |
| + if (!(retval = put_user(cred->uid, ruid)) && | |
| + !(retval = put_user(cred->euid, euid))) | |
| + retval = put_user(cred->suid, suid); | |
| return retval; | |
| } | |
| @@ -762,50 +826,53 @@ asmlinkage long sys_getresuid(uid_t __user *ruid, uid_t __user *euid, uid_t __us | |
| /* | |
| * Same as above, but for rgid, egid, sgid. | |
| */ | |
| -asmlinkage long sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid) | |
| +SYSCALL_DEFINE3(setresgid, gid_t, rgid, gid_t, egid, gid_t, sgid) | |
| { | |
| + const struct cred *old; | |
| + struct cred *new; | |
| int retval; | |
| - retval = security_task_setgid(rgid, egid, sgid, LSM_SETID_RES); | |
| - if (retval) | |
| - return retval; | |
| - | |
| - if (!capable(CAP_SETGID)) { | |
| - if ((rgid != (gid_t) -1) && (rgid != current->gid) && | |
| - (rgid != current->egid) && (rgid != current->sgid)) | |
| - return -EPERM; | |
| - if ((egid != (gid_t) -1) && (egid != current->gid) && | |
| - (egid != current->egid) && (egid != current->sgid)) | |
| - return -EPERM; | |
| - if ((sgid != (gid_t) -1) && (sgid != current->gid) && | |
| - (sgid != current->egid) && (sgid != current->sgid)) | |
| - return -EPERM; | |
| - } | |
| - if (egid != (gid_t) -1) { | |
| - if (egid != current->egid) { | |
| - set_dumpable(current->mm, suid_dumpable); | |
| - smp_wmb(); | |
| - } | |
| - current->egid = egid; | |
| + new = prepare_creds(); | |
| + if (!new) | |
| + return -ENOMEM; | |
| + old = current_cred(); | |
| + | |
| + retval = -EPERM; | |
| + if (!nsown_capable(CAP_SETGID)) { | |
| + if (rgid != (gid_t) -1 && rgid != old->gid && | |
| + rgid != old->egid && rgid != old->sgid) | |
| + goto error; | |
| + if (egid != (gid_t) -1 && egid != old->gid && | |
| + egid != old->egid && egid != old->sgid) | |
| + goto error; | |
| + if (sgid != (gid_t) -1 && sgid != old->gid && | |
| + sgid != old->egid && sgid != old->sgid) | |
| + goto error; | |
| } | |
| - current->fsgid = current->egid; | |
| + | |
| if (rgid != (gid_t) -1) | |
| - current->gid = rgid; | |
| + new->gid = rgid; | |
| + if (egid != (gid_t) -1) | |
| + new->egid = egid; | |
| if (sgid != (gid_t) -1) | |
| - current->sgid = sgid; | |
| + new->sgid = sgid; | |
| + new->fsgid = new->egid; | |
| - key_fsgid_changed(current); | |
| - proc_id_connector(current, PROC_EVENT_GID); | |
| - return 0; | |
| + return commit_creds(new); | |
| + | |
| +error: | |
| + abort_creds(new); | |
| + return retval; | |
| } | |
| -asmlinkage long sys_getresgid(gid_t __user *rgid, gid_t __user *egid, gid_t __user *sgid) | |
| +SYSCALL_DEFINE3(getresgid, gid_t __user *, rgid, gid_t __user *, egid, gid_t __user *, sgid) | |
| { | |
| + const struct cred *cred = current_cred(); | |
| int retval; | |
| - if (!(retval = put_user(current->gid, rgid)) && | |
| - !(retval = put_user(current->egid, egid))) | |
| - retval = put_user(current->sgid, sgid); | |
| + if (!(retval = put_user(cred->gid, rgid)) && | |
| + !(retval = put_user(cred->egid, egid))) | |
| + retval = put_user(cred->sgid, sgid); | |
| return retval; | |
| } | |
| @@ -817,92 +884,93 @@ asmlinkage long sys_getresgid(gid_t __user *rgid, gid_t __user *egid, gid_t __us | |
| * whatever uid it wants to). It normally shadows "euid", except when | |
| * explicitly set by setfsuid() or for access.. | |
| */ | |
| -asmlinkage long sys_setfsuid(uid_t uid) | |
| +SYSCALL_DEFINE1(setfsuid, uid_t, uid) | |
| { | |
| - int old_fsuid; | |
| - | |
| - old_fsuid = current->fsuid; | |
| - if (security_task_setuid(uid, (uid_t)-1, (uid_t)-1, LSM_SETID_FS)) | |
| - return old_fsuid; | |
| - | |
| - if (uid == current->uid || uid == current->euid || | |
| - uid == current->suid || uid == current->fsuid || | |
| - capable(CAP_SETUID)) { | |
| + const struct cred *old; | |
| + struct cred *new; | |
| + uid_t old_fsuid; | |
| + | |
| + new = prepare_creds(); | |
| + if (!new) | |
| + return current_fsuid(); | |
| + old = current_cred(); | |
| + old_fsuid = old->fsuid; | |
| + | |
| + if (uid == old->uid || uid == old->euid || | |
| + uid == old->suid || uid == old->fsuid || | |
| + nsown_capable(CAP_SETUID)) { | |
| if (uid != old_fsuid) { | |
| - set_dumpable(current->mm, suid_dumpable); | |
| - smp_wmb(); | |
| + new->fsuid = uid; | |
| + if (security_task_fix_setuid(new, old, LSM_SETID_FS) == 0) | |
| + goto change_okay; | |
| } | |
| - current->fsuid = uid; | |
| } | |
| - key_fsuid_changed(current); | |
| - proc_id_connector(current, PROC_EVENT_UID); | |
| - | |
| - security_task_post_setuid(old_fsuid, (uid_t)-1, (uid_t)-1, LSM_SETID_FS); | |
| + abort_creds(new); | |
| + return old_fsuid; | |
| +change_okay: | |
| + commit_creds(new); | |
| return old_fsuid; | |
| } | |
| /* | |
| * Samma på svenska.. | |
| */ | |
| -asmlinkage long sys_setfsgid(gid_t gid) | |
| +SYSCALL_DEFINE1(setfsgid, gid_t, gid) | |
| { | |
| - int old_fsgid; | |
| - | |
| - old_fsgid = current->fsgid; | |
| - if (security_task_setgid(gid, (gid_t)-1, (gid_t)-1, LSM_SETID_FS)) | |
| - return old_fsgid; | |
| - | |
| - if (gid == current->gid || gid == current->egid || | |
| - gid == current->sgid || gid == current->fsgid || | |
| - capable(CAP_SETGID)) { | |
| + const struct cred *old; | |
| + struct cred *new; | |
| + gid_t old_fsgid; | |
| + | |
| + new = prepare_creds(); | |
| + if (!new) | |
| + return current_fsgid(); | |
| + old = current_cred(); | |
| + old_fsgid = old->fsgid; | |
| + | |
| + if (gid == old->gid || gid == old->egid || | |
| + gid == old->sgid || gid == old->fsgid || | |
| + nsown_capable(CAP_SETGID)) { | |
| if (gid != old_fsgid) { | |
| - set_dumpable(current->mm, suid_dumpable); | |
| - smp_wmb(); | |
| + new->fsgid = gid; | |
| + goto change_okay; | |
| } | |
| - current->fsgid = gid; | |
| - key_fsgid_changed(current); | |
| - proc_id_connector(current, PROC_EVENT_GID); | |
| } | |
| + | |
| + abort_creds(new); | |
| + return old_fsgid; | |
| + | |
| +change_okay: | |
| + commit_creds(new); | |
| return old_fsgid; | |
| } | |
| -asmlinkage long sys_times(struct tms __user * tbuf) | |
| +void do_sys_times(struct tms *tms) | |
| +{ | |
| + cputime_t tgutime, tgstime, cutime, cstime; | |
| + | |
| + spin_lock_irq(¤t->sighand->siglock); | |
| + thread_group_times(current, &tgutime, &tgstime); | |
| + cutime = current->signal->cutime; | |
| + cstime = current->signal->cstime; | |
| + spin_unlock_irq(¤t->sighand->siglock); | |
| + tms->tms_utime = cputime_to_clock_t(tgutime); | |
| + tms->tms_stime = cputime_to_clock_t(tgstime); | |
| + tms->tms_cutime = cputime_to_clock_t(cutime); | |
| + tms->tms_cstime = cputime_to_clock_t(cstime); | |
| +} | |
| + | |
| +SYSCALL_DEFINE1(times, struct tms __user *, tbuf) | |
| { | |
| - /* | |
| - * In the SMP world we might just be unlucky and have one of | |
| - * the times increment as we use it. Since the value is an | |
| - * atomically safe type this is just fine. Conceptually its | |
| - * as if the syscall took an instant longer to occur. | |
| - */ | |
| if (tbuf) { | |
| struct tms tmp; | |
| - struct task_struct *tsk = current; | |
| - struct task_struct *t; | |
| - cputime_t utime, stime, cutime, cstime; | |
| - | |
| - spin_lock_irq(&tsk->sighand->siglock); | |
| - utime = tsk->signal->utime; | |
| - stime = tsk->signal->stime; | |
| - t = tsk; | |
| - do { | |
| - utime = cputime_add(utime, t->utime); | |
| - stime = cputime_add(stime, t->stime); | |
| - t = next_thread(t); | |
| - } while (t != tsk); | |
| - | |
| - cutime = tsk->signal->cutime; | |
| - cstime = tsk->signal->cstime; | |
| - spin_unlock_irq(&tsk->sighand->siglock); | |
| - | |
| - tmp.tms_utime = cputime_to_clock_t(utime); | |
| - tmp.tms_stime = cputime_to_clock_t(stime); | |
| - tmp.tms_cutime = cputime_to_clock_t(cutime); | |
| - tmp.tms_cstime = cputime_to_clock_t(cstime); | |
| + | |
| + do_sys_times(&tmp); | |
| if (copy_to_user(tbuf, &tmp, sizeof(struct tms))) | |
| return -EFAULT; | |
| } | |
| + force_successful_syscall_return(); | |
| return (long) jiffies_64_to_clock_t(get_jiffies_64()); | |
| } | |
| @@ -918,7 +986,7 @@ asmlinkage long sys_times(struct tms __user * tbuf) | |
| * Auch. Had to add the 'did_exec' flag to conform completely to POSIX. | |
| * LBT 04.03.94 | |
| */ | |
| -asmlinkage long sys_setpgid(pid_t pid, pid_t pgid) | |
| +SYSCALL_DEFINE2(setpgid, pid_t, pid, pid_t, pgid) | |
| { | |
| struct task_struct *p; | |
| struct task_struct *group_leader = current->group_leader; | |
| @@ -931,6 +999,7 @@ asmlinkage long sys_setpgid(pid_t pid, pid_t pgid) | |
| pgid = pid; | |
| if (pgid < 0) | |
| return -EINVAL; | |
| + rcu_read_lock(); | |
| /* From this point forward we keep holding onto the tasklist lock | |
| * so that our parent does not change from under us. -DaveM | |
| @@ -977,19 +1046,18 @@ asmlinkage long sys_setpgid(pid_t pid, pid_t pgid) | |
| if (err) | |
| goto out; | |
| - if (task_pgrp(p) != pgrp) { | |
| + if (task_pgrp(p) != pgrp) | |
| change_pid(p, PIDTYPE_PGID, pgrp); | |
| - set_task_pgrp(p, pid_nr(pgrp)); | |
| - } | |
| err = 0; | |
| out: | |
| /* All paths lead to here, thus we are safe. -DaveM */ | |
| write_unlock_irq(&tasklist_lock); | |
| + rcu_read_unlock(); | |
| return err; | |
| } | |
| -asmlinkage long sys_getpgid(pid_t pid) | |
| +SYSCALL_DEFINE1(getpgid, pid_t, pid) | |
| { | |
| struct task_struct *p; | |
| struct pid *grp; | |
| @@ -1019,14 +1087,14 @@ asmlinkage long sys_getpgid(pid_t pid) | |
| #ifdef __ARCH_WANT_SYS_GETPGRP | |
| -asmlinkage long sys_getpgrp(void) | |
| +SYSCALL_DEFINE0(getpgrp) | |
| { | |
| return sys_getpgid(0); | |
| } | |
| #endif | |
| -asmlinkage long sys_getsid(pid_t pid) | |
| +SYSCALL_DEFINE1(getsid, pid_t, pid) | |
| { | |
| struct task_struct *p; | |
| struct pid *sid; | |
| @@ -1054,7 +1122,7 @@ asmlinkage long sys_getsid(pid_t pid) | |
| return retval; | |
| } | |
| -asmlinkage long sys_setsid(void) | |
| +SYSCALL_DEFINE0(setsid) | |
| { | |
| struct task_struct *group_leader = current->group_leader; | |
| struct pid *sid = task_pid(group_leader); | |
| @@ -1075,321 +1143,171 @@ asmlinkage long sys_setsid(void) | |
| group_leader->signal->leader = 1; | |
| __set_special_pids(sid); | |
| - spin_lock(&group_leader->sighand->siglock); | |
| - group_leader->signal->tty = NULL; | |
| - spin_unlock(&group_leader->sighand->siglock); | |
| + proc_clear_tty(group_leader); | |
| err = session; | |
| out: | |
| write_unlock_irq(&tasklist_lock); | |
| - return err; | |
| -} | |
| - | |
| -/* | |
| - * Supplementary group IDs | |
| - */ | |
| - | |
| -/* init to 2 - one for init_task, one to ensure it is never freed */ | |
| -struct group_info init_groups = { .usage = ATOMIC_INIT(2) }; | |
| - | |
| -struct group_info *groups_alloc(int gidsetsize) | |
| -{ | |
| - struct group_info *group_info; | |
| - int nblocks; | |
| - int i; | |
| - | |
| - nblocks = (gidsetsize + NGROUPS_PER_BLOCK - 1) / NGROUPS_PER_BLOCK; | |
| - /* Make sure we always allocate at least one indirect block pointer */ | |
| - nblocks = nblocks ? : 1; | |
| - group_info = kmalloc(sizeof(*group_info) + nblocks*sizeof(gid_t *), GFP_USER); | |
| - if (!group_info) | |
| - return NULL; | |
| - group_info->ngroups = gidsetsize; | |
| - group_info->nblocks = nblocks; | |
| - atomic_set(&group_info->usage, 1); | |
| - | |
| - if (gidsetsize <= NGROUPS_SMALL) | |
| - group_info->blocks[0] = group_info->small_block; | |
| - else { | |
| - for (i = 0; i < nblocks; i++) { | |
| - gid_t *b; | |
| - b = (void *)__get_free_page(GFP_USER); | |
| - if (!b) | |
| - goto out_undo_partial_alloc; | |
| - group_info->blocks[i] = b; | |
| - } | |
| + if (err > 0) { | |
| + proc_sid_connector(group_leader); | |
| + sched_autogroup_create_attach(group_leader); | |
| } | |
| - return group_info; | |
| - | |
| -out_undo_partial_alloc: | |
| - while (--i >= 0) { | |
| - free_page((unsigned long)group_info->blocks[i]); | |
| - } | |
| - kfree(group_info); | |
| - return NULL; | |
| -} | |
| - | |
| -EXPORT_SYMBOL(groups_alloc); | |
| - | |
| -void groups_free(struct group_info *group_info) | |
| -{ | |
| - if (group_info->blocks[0] != group_info->small_block) { | |
| - int i; | |
| - for (i = 0; i < group_info->nblocks; i++) | |
| - free_page((unsigned long)group_info->blocks[i]); | |
| - } | |
| - kfree(group_info); | |
| + return err; | |
| } | |
| -EXPORT_SYMBOL(groups_free); | |
| - | |
| -/* export the group_info to a user-space array */ | |
| -static int groups_to_user(gid_t __user *grouplist, | |
| - struct group_info *group_info) | |
| -{ | |
| - int i; | |
| - unsigned int count = group_info->ngroups; | |
| - | |
| - for (i = 0; i < group_info->nblocks; i++) { | |
| - unsigned int cp_count = min(NGROUPS_PER_BLOCK, count); | |
| - unsigned int len = cp_count * sizeof(*grouplist); | |
| - | |
| - if (copy_to_user(grouplist, group_info->blocks[i], len)) | |
| - return -EFAULT; | |
| +DECLARE_RWSEM(uts_sem); | |
| - grouplist += NGROUPS_PER_BLOCK; | |
| - count -= cp_count; | |
| - } | |
| - return 0; | |
| -} | |
| +#ifdef COMPAT_UTS_MACHINE | |
| +#define override_architecture(name) \ | |
| + (personality(current->personality) == PER_LINUX32 && \ | |
| + copy_to_user(name->machine, COMPAT_UTS_MACHINE, \ | |
| + sizeof(COMPAT_UTS_MACHINE))) | |
| +#else | |
| +#define override_architecture(name) 0 | |
| +#endif | |
| -/* fill a group_info from a user-space array - it must be allocated already */ | |
| -static int groups_from_user(struct group_info *group_info, | |
| - gid_t __user *grouplist) | |
| +/* | |
| + * Work around broken programs that cannot handle "Linux 3.0". | |
| + * Instead we map 3.x to 2.6.40+x, so e.g. 3.0 would be 2.6.40 | |
| + */ | |
| +static int override_release(char __user *release, int len) | |
| { | |
| - int i; | |
| - unsigned int count = group_info->ngroups; | |
| - | |
| - for (i = 0; i < group_info->nblocks; i++) { | |
| - unsigned int cp_count = min(NGROUPS_PER_BLOCK, count); | |
| - unsigned int len = cp_count * sizeof(*grouplist); | |
| - | |
| - if (copy_from_user(group_info->blocks[i], grouplist, len)) | |
| - return -EFAULT; | |
| + int ret = 0; | |
| + char buf[65]; | |
| - grouplist += NGROUPS_PER_BLOCK; | |
| - count -= cp_count; | |
| - } | |
| - return 0; | |
| -} | |
| + if (current->personality & UNAME26) { | |
| + char *rest = UTS_RELEASE; | |
| + int ndots = 0; | |
| + unsigned v; | |
| -/* a simple Shell sort */ | |
| -static void groups_sort(struct group_info *group_info) | |
| -{ | |
| - int base, max, stride; | |
| - int gidsetsize = group_info->ngroups; | |
| - | |
| - for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1) | |
| - ; /* nothing */ | |
| - stride /= 3; | |
| - | |
| - while (stride) { | |
| - max = gidsetsize - stride; | |
| - for (base = 0; base < max; base++) { | |
| - int left = base; | |
| - int right = left + stride; | |
| - gid_t tmp = GROUP_AT(group_info, right); | |
| - | |
| - while (left >= 0 && GROUP_AT(group_info, left) > tmp) { | |
| - GROUP_AT(group_info, right) = | |
| - GROUP_AT(group_info, left); | |
| - right = left; | |
| - left -= stride; | |
| - } | |
| - GROUP_AT(group_info, right) = tmp; | |
| + while (*rest) { | |
| + if (*rest == '.' && ++ndots >= 3) | |
| + break; | |
| + if (!isdigit(*rest) && *rest != '.') | |
| + break; | |
| + rest++; | |
| } | |
| - stride /= 3; | |
| - } | |
| -} | |
| - | |
| -/* a simple bsearch */ | |
| -int groups_search(struct group_info *group_info, gid_t grp) | |
| -{ | |
| - unsigned int left, right; | |
| - | |
| - if (!group_info) | |
| - return 0; | |
| - | |
| - left = 0; | |
| - right = group_info->ngroups; | |
| - while (left < right) { | |
| - unsigned int mid = (left+right)/2; | |
| - int cmp = grp - GROUP_AT(group_info, mid); | |
| - if (cmp > 0) | |
| - left = mid + 1; | |
| - else if (cmp < 0) | |
| - right = mid; | |
| - else | |
| - return 1; | |
| + v = ((LINUX_VERSION_CODE >> 8) & 0xff) + 40; | |
| + snprintf(buf, len, "2.6.%u%s", v, rest); | |
| + ret = copy_to_user(release, buf, len); | |
| } | |
| - return 0; | |
| -} | |
| - | |
| -/* validate and set current->group_info */ | |
| -int set_current_groups(struct group_info *group_info) | |
| -{ | |
| - int retval; | |
| - struct group_info *old_info; | |
| - | |
| - retval = security_task_setgroups(group_info); | |
| - if (retval) | |
| - return retval; | |
| - | |
| - groups_sort(group_info); | |
| - get_group_info(group_info); | |
| - | |
| - task_lock(current); | |
| - old_info = current->group_info; | |
| - current->group_info = group_info; | |
| - task_unlock(current); | |
| - | |
| - put_group_info(old_info); | |
| - | |
| - return 0; | |
| + return ret; | |
| } | |
| -EXPORT_SYMBOL(set_current_groups); | |
| - | |
| -asmlinkage long sys_getgroups(int gidsetsize, gid_t __user *grouplist) | |
| +SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name) | |
| { | |
| - int i = 0; | |
| - | |
| - /* | |
| - * SMP: Nobody else can change our grouplist. Thus we are | |
| - * safe. | |
| - */ | |
| + int errno = 0; | |
| - if (gidsetsize < 0) | |
| - return -EINVAL; | |
| + down_read(&uts_sem); | |
| + if (copy_to_user(name, utsname(), sizeof *name)) | |
| + errno = -EFAULT; | |
| + up_read(&uts_sem); | |
| - /* no need to grab task_lock here; it cannot change */ | |
| - i = current->group_info->ngroups; | |
| - if (gidsetsize) { | |
| - if (i > gidsetsize) { | |
| - i = -EINVAL; | |
| - goto out; | |
| - } | |
| - if (groups_to_user(grouplist, current->group_info)) { | |
| - i = -EFAULT; | |
| - goto out; | |
| - } | |
| - } | |
| -out: | |
| - return i; | |
| + if (!errno && override_release(name->release, sizeof(name->release))) | |
| + errno = -EFAULT; | |
| + if (!errno && override_architecture(name)) | |
| + errno = -EFAULT; | |
| + return errno; | |
| } | |
| +#ifdef __ARCH_WANT_SYS_OLD_UNAME | |
| /* | |
| - * SMP: Our groups are copy-on-write. We can set them safely | |
| - * without another task interfering. | |
| + * Old cruft | |
| */ | |
| - | |
| -asmlinkage long sys_setgroups(int gidsetsize, gid_t __user *grouplist) | |
| +SYSCALL_DEFINE1(uname, struct old_utsname __user *, name) | |
| { | |
| - struct group_info *group_info; | |
| - int retval; | |
| - | |
| - if (!capable(CAP_SETGID)) | |
| - return -EPERM; | |
| - if ((unsigned)gidsetsize > NGROUPS_MAX) | |
| - return -EINVAL; | |
| + int error = 0; | |
| - group_info = groups_alloc(gidsetsize); | |
| - if (!group_info) | |
| - return -ENOMEM; | |
| - retval = groups_from_user(group_info, grouplist); | |
| - if (retval) { | |
| - put_group_info(group_info); | |
| - return retval; | |
| - } | |
| - | |
| - retval = set_current_groups(group_info); | |
| - put_group_info(group_info); | |
| + if (!name) | |
| + return -EFAULT; | |
| - return retval; | |
| -} | |
| + down_read(&uts_sem); | |
| + if (copy_to_user(name, utsname(), sizeof(*name))) | |
| + error = -EFAULT; | |
| + up_read(&uts_sem); | |
| -/* | |
| - * Check whether we're fsgid/egid or in the supplemental group.. | |
| - */ | |
| -int in_group_p(gid_t grp) | |
| -{ | |
| - int retval = 1; | |
| - if (grp != current->fsgid) | |
| - retval = groups_search(current->group_info, grp); | |
| - return retval; | |
| + if (!error && override_release(name->release, sizeof(name->release))) | |
| + error = -EFAULT; | |
| + if (!error && override_architecture(name)) | |
| + error = -EFAULT; | |
| + return error; | |
| } | |
| -EXPORT_SYMBOL(in_group_p); | |
| - | |
| -int in_egroup_p(gid_t grp) | |
| +SYSCALL_DEFINE1(olduname, struct oldold_utsname __user *, name) | |
| { | |
| - int retval = 1; | |
| - if (grp != current->egid) | |
| - retval = groups_search(current->group_info, grp); | |
| - return retval; | |
| -} | |
| + int error; | |
| -EXPORT_SYMBOL(in_egroup_p); | |
| - | |
| -DECLARE_RWSEM(uts_sem); | |
| - | |
| -EXPORT_SYMBOL(uts_sem); | |
| - | |
| -asmlinkage long sys_newuname(struct new_utsname __user * name) | |
| -{ | |
| - int errno = 0; | |
| + if (!name) | |
| + return -EFAULT; | |
| + if (!access_ok(VERIFY_WRITE, name, sizeof(struct oldold_utsname))) | |
| + return -EFAULT; | |
| down_read(&uts_sem); | |
| - if (copy_to_user(name, utsname(), sizeof *name)) | |
| - errno = -EFAULT; | |
| + error = __copy_to_user(&name->sysname, &utsname()->sysname, | |
| + __OLD_UTS_LEN); | |
| + error |= __put_user(0, name->sysname + __OLD_UTS_LEN); | |
| + error |= __copy_to_user(&name->nodename, &utsname()->nodename, | |
| + __OLD_UTS_LEN); | |
| + error |= __put_user(0, name->nodename + __OLD_UTS_LEN); | |
| + error |= __copy_to_user(&name->release, &utsname()->release, | |
| + __OLD_UTS_LEN); | |
| + error |= __put_user(0, name->release + __OLD_UTS_LEN); | |
| + error |= __copy_to_user(&name->version, &utsname()->version, | |
| + __OLD_UTS_LEN); | |
| + error |= __put_user(0, name->version + __OLD_UTS_LEN); | |
| + error |= __copy_to_user(&name->machine, &utsname()->machine, | |
| + __OLD_UTS_LEN); | |
| + error |= __put_user(0, name->machine + __OLD_UTS_LEN); | |
| up_read(&uts_sem); | |
| - return errno; | |
| + | |
| + if (!error && override_architecture(name)) | |
| + error = -EFAULT; | |
| + if (!error && override_release(name->release, sizeof(name->release))) | |
| + error = -EFAULT; | |
| + return error ? -EFAULT : 0; | |
| } | |
| +#endif | |
| -asmlinkage long sys_sethostname(char __user *name, int len) | |
| +SYSCALL_DEFINE2(sethostname, char __user *, name, int, len) | |
| { | |
| int errno; | |
| char tmp[__NEW_UTS_LEN]; | |
| - if (!capable(CAP_SYS_ADMIN)) | |
| + if (!ns_capable(current->nsproxy->uts_ns->user_ns, CAP_SYS_ADMIN)) | |
| return -EPERM; | |
| + | |
| if (len < 0 || len > __NEW_UTS_LEN) | |
| return -EINVAL; | |
| down_write(&uts_sem); | |
| errno = -EFAULT; | |
| if (!copy_from_user(tmp, name, len)) { | |
| - memcpy(utsname()->nodename, tmp, len); | |
| - utsname()->nodename[len] = 0; | |
| + struct new_utsname *u = utsname(); | |
| + | |
| + memcpy(u->nodename, tmp, len); | |
| + memset(u->nodename + len, 0, sizeof(u->nodename) - len); | |
| errno = 0; | |
| } | |
| + uts_proc_notify(UTS_PROC_HOSTNAME); | |
| up_write(&uts_sem); | |
| return errno; | |
| } | |
| #ifdef __ARCH_WANT_SYS_GETHOSTNAME | |
| -asmlinkage long sys_gethostname(char __user *name, int len) | |
| +SYSCALL_DEFINE2(gethostname, char __user *, name, int, len) | |
| { | |
| int i, errno; | |
| + struct new_utsname *u; | |
| if (len < 0) | |
| return -EINVAL; | |
| down_read(&uts_sem); | |
| - i = 1 + strlen(utsname()->nodename); | |
| + u = utsname(); | |
| + i = 1 + strlen(u->nodename); | |
| if (i > len) | |
| i = len; | |
| errno = 0; | |
| - if (copy_to_user(name, utsname()->nodename, i)) | |
| + if (copy_to_user(name, u->nodename, i)) | |
| errno = -EFAULT; | |
| up_read(&uts_sem); | |
| return errno; | |
| @@ -1401,12 +1319,12 @@ asmlinkage long sys_gethostname(char __user *name, int len) | |
| * Only setdomainname; getdomainname can be implemented by calling | |
| * uname() | |
| */ | |
| -asmlinkage long sys_setdomainname(char __user *name, int len) | |
| +SYSCALL_DEFINE2(setdomainname, char __user *, name, int, len) | |
| { | |
| int errno; | |
| char tmp[__NEW_UTS_LEN]; | |
| - if (!capable(CAP_SYS_ADMIN)) | |
| + if (!ns_capable(current->nsproxy->uts_ns->user_ns, CAP_SYS_ADMIN)) | |
| return -EPERM; | |
| if (len < 0 || len > __NEW_UTS_LEN) | |
| return -EINVAL; | |
| @@ -1414,25 +1332,27 @@ asmlinkage long sys_setdomainname(char __user *name, int len) | |
| down_write(&uts_sem); | |
| errno = -EFAULT; | |
| if (!copy_from_user(tmp, name, len)) { | |
| - memcpy(utsname()->domainname, tmp, len); | |
| - utsname()->domainname[len] = 0; | |
| + struct new_utsname *u = utsname(); | |
| + | |
| + memcpy(u->domainname, tmp, len); | |
| + memset(u->domainname + len, 0, sizeof(u->domainname) - len); | |
| errno = 0; | |
| } | |
| + uts_proc_notify(UTS_PROC_DOMAINNAME); | |
| up_write(&uts_sem); | |
| return errno; | |
| } | |
| -asmlinkage long sys_getrlimit(unsigned int resource, struct rlimit __user *rlim) | |
| +SYSCALL_DEFINE2(getrlimit, unsigned int, resource, struct rlimit __user *, rlim) | |
| { | |
| - if (resource >= RLIM_NLIMITS) | |
| - return -EINVAL; | |
| - else { | |
| - struct rlimit value; | |
| - task_lock(current->group_leader); | |
| - value = current->signal->rlim[resource]; | |
| - task_unlock(current->group_leader); | |
| - return copy_to_user(rlim, &value, sizeof(*rlim)) ? -EFAULT : 0; | |
| - } | |
| + struct rlimit value; | |
| + int ret; | |
| + | |
| + ret = do_prlimit(current, resource, NULL, &value); | |
| + if (!ret) | |
| + ret = copy_to_user(rlim, &value, sizeof(*rlim)) ? -EFAULT : 0; | |
| + | |
| + return ret; | |
| } | |
| #ifdef __ARCH_WANT_SYS_OLD_GETRLIMIT | |
| @@ -1441,7 +1361,8 @@ asmlinkage long sys_getrlimit(unsigned int resource, struct rlimit __user *rlim) | |
| * Back compatibility for getrlimit. Needed for some apps. | |
| */ | |
| -asmlinkage long sys_old_getrlimit(unsigned int resource, struct rlimit __user *rlim) | |
| +SYSCALL_DEFINE2(old_getrlimit, unsigned int, resource, | |
| + struct rlimit __user *, rlim) | |
| { | |
| struct rlimit x; | |
| if (resource >= RLIM_NLIMITS) | |
| @@ -1459,45 +1380,91 @@ asmlinkage long sys_old_getrlimit(unsigned int resource, struct rlimit __user *r | |
| #endif | |
| -asmlinkage long sys_setrlimit(unsigned int resource, struct rlimit __user *rlim) | |
| +static inline bool rlim64_is_infinity(__u64 rlim64) | |
| { | |
| - struct rlimit new_rlim, *old_rlim; | |
| - unsigned long it_prof_secs; | |
| - int retval; | |
| +#if BITS_PER_LONG < 64 | |
| + return rlim64 >= ULONG_MAX; | |
| +#else | |
| + return rlim64 == RLIM64_INFINITY; | |
| +#endif | |
| +} | |
| + | |
| +static void rlim_to_rlim64(const struct rlimit *rlim, struct rlimit64 *rlim64) | |
| +{ | |
| + if (rlim->rlim_cur == RLIM_INFINITY) | |
| + rlim64->rlim_cur = RLIM64_INFINITY; | |
| + else | |
| + rlim64->rlim_cur = rlim->rlim_cur; | |
| + if (rlim->rlim_max == RLIM_INFINITY) | |
| + rlim64->rlim_max = RLIM64_INFINITY; | |
| + else | |
| + rlim64->rlim_max = rlim->rlim_max; | |
| +} | |
| + | |
| +static void rlim64_to_rlim(const struct rlimit64 *rlim64, struct rlimit *rlim) | |
| +{ | |
| + if (rlim64_is_infinity(rlim64->rlim_cur)) | |
| + rlim->rlim_cur = RLIM_INFINITY; | |
| + else | |
| + rlim->rlim_cur = (unsigned long)rlim64->rlim_cur; | |
| + if (rlim64_is_infinity(rlim64->rlim_max)) | |
| + rlim->rlim_max = RLIM_INFINITY; | |
| + else | |
| + rlim->rlim_max = (unsigned long)rlim64->rlim_max; | |
| +} | |
| + | |
| +/* make sure you are allowed to change @tsk limits before calling this */ | |
| +int do_prlimit(struct task_struct *tsk, unsigned int resource, | |
| + struct rlimit *new_rlim, struct rlimit *old_rlim) | |
| +{ | |
| + struct rlimit *rlim; | |
| + int retval = 0; | |
| if (resource >= RLIM_NLIMITS) | |
| return -EINVAL; | |
| - if (copy_from_user(&new_rlim, rlim, sizeof(*rlim))) | |
| - return -EFAULT; | |
| - if (new_rlim.rlim_cur > new_rlim.rlim_max) | |
| - return -EINVAL; | |
| - old_rlim = current->signal->rlim + resource; | |
| - if ((new_rlim.rlim_max > old_rlim->rlim_max) && | |
| - !capable(CAP_SYS_RESOURCE)) | |
| - return -EPERM; | |
| - if (resource == RLIMIT_NOFILE && new_rlim.rlim_max > sysctl_nr_open) | |
| - return -EPERM; | |
| - | |
| - retval = security_task_setrlimit(resource, &new_rlim); | |
| - if (retval) | |
| - return retval; | |
| - | |
| - if (resource == RLIMIT_CPU && new_rlim.rlim_cur == 0) { | |
| - /* | |
| - * The caller is asking for an immediate RLIMIT_CPU | |
| - * expiry. But we use the zero value to mean "it was | |
| - * never set". So let's cheat and make it one second | |
| - * instead | |
| - */ | |
| - new_rlim.rlim_cur = 1; | |
| + if (new_rlim) { | |
| + if (new_rlim->rlim_cur > new_rlim->rlim_max) | |
| + return -EINVAL; | |
| + if (resource == RLIMIT_NOFILE && | |
| + new_rlim->rlim_max > sysctl_nr_open) | |
| + return -EPERM; | |
| } | |
| - task_lock(current->group_leader); | |
| - *old_rlim = new_rlim; | |
| - task_unlock(current->group_leader); | |
| - | |
| - if (resource != RLIMIT_CPU) | |
| + /* protect tsk->signal and tsk->sighand from disappearing */ | |
| + read_lock(&tasklist_lock); | |
| + if (!tsk->sighand) { | |
| + retval = -ESRCH; | |
| goto out; | |
| + } | |
| + | |
| + rlim = tsk->signal->rlim + resource; | |
| + task_lock(tsk->group_leader); | |
| + if (new_rlim) { | |
| + /* Keep the capable check against init_user_ns until | |
| + cgroups can contain all limits */ | |
| + if (new_rlim->rlim_max > rlim->rlim_max && | |
| + !capable(CAP_SYS_RESOURCE)) | |
| + retval = -EPERM; | |
| + if (!retval) | |
| + retval = security_task_setrlimit(tsk->group_leader, | |
| + resource, new_rlim); | |
| + if (resource == RLIMIT_CPU && new_rlim->rlim_cur == 0) { | |
| + /* | |
| + * The caller is asking for an immediate RLIMIT_CPU | |
| + * expiry. But we use the zero value to mean "it was | |
| + * never set". So let's cheat and make it one second | |
| + * instead | |
| + */ | |
| + new_rlim->rlim_cur = 1; | |
| + } | |
| + } | |
| + if (!retval) { | |
| + if (old_rlim) | |
| + *old_rlim = *rlim; | |
| + if (new_rlim) | |
| + *rlim = *new_rlim; | |
| + } | |
| + task_unlock(tsk->group_leader); | |
| /* | |
| * RLIMIT_CPU handling. Note that the kernel fails to return an error | |
| @@ -1505,23 +1472,86 @@ asmlinkage long sys_setrlimit(unsigned int resource, struct rlimit __user *rlim) | |
| * very long-standing error, and fixing it now risks breakage of | |
| * applications, so we live with it | |
| */ | |
| - if (new_rlim.rlim_cur == RLIM_INFINITY) | |
| - goto out; | |
| + if (!retval && new_rlim && resource == RLIMIT_CPU && | |
| + new_rlim->rlim_cur != RLIM_INFINITY) | |
| + update_rlimit_cpu(tsk, new_rlim->rlim_cur); | |
| +out: | |
| + read_unlock(&tasklist_lock); | |
| + return retval; | |
| +} | |
| + | |
| +/* rcu lock must be held */ | |
| +static int check_prlimit_permission(struct task_struct *task) | |
| +{ | |
| + const struct cred *cred = current_cred(), *tcred; | |
| + | |
| + if (current == task) | |
| + return 0; | |
| + | |
| + tcred = __task_cred(task); | |
| + if (cred->user->user_ns == tcred->user->user_ns && | |
| + (cred->uid == tcred->euid && | |
| + cred->uid == tcred->suid && | |
| + cred->uid == tcred->uid && | |
| + cred->gid == tcred->egid && | |
| + cred->gid == tcred->sgid && | |
| + cred->gid == tcred->gid)) | |
| + return 0; | |
| + if (ns_capable(tcred->user->user_ns, CAP_SYS_RESOURCE)) | |
| + return 0; | |
| + | |
| + return -EPERM; | |
| +} | |
| - it_prof_secs = cputime_to_secs(current->signal->it_prof_expires); | |
| - if (it_prof_secs == 0 || new_rlim.rlim_cur <= it_prof_secs) { | |
| - unsigned long rlim_cur = new_rlim.rlim_cur; | |
| - cputime_t cputime; | |
| - | |
| - cputime = secs_to_cputime(rlim_cur); | |
| - read_lock(&tasklist_lock); | |
| - spin_lock_irq(¤t->sighand->siglock); | |
| - set_process_cpu_timer(current, CPUCLOCK_PROF, &cputime, NULL); | |
| - spin_unlock_irq(¤t->sighand->siglock); | |
| - read_unlock(&tasklist_lock); | |
| +SYSCALL_DEFINE4(prlimit64, pid_t, pid, unsigned int, resource, | |
| + const struct rlimit64 __user *, new_rlim, | |
| + struct rlimit64 __user *, old_rlim) | |
| +{ | |
| + struct rlimit64 old64, new64; | |
| + struct rlimit old, new; | |
| + struct task_struct *tsk; | |
| + int ret; | |
| + | |
| + if (new_rlim) { | |
| + if (copy_from_user(&new64, new_rlim, sizeof(new64))) | |
| + return -EFAULT; | |
| + rlim64_to_rlim(&new64, &new); | |
| } | |
| -out: | |
| - return 0; | |
| + | |
| + rcu_read_lock(); | |
| + tsk = pid ? find_task_by_vpid(pid) : current; | |
| + if (!tsk) { | |
| + rcu_read_unlock(); | |
| + return -ESRCH; | |
| + } | |
| + ret = check_prlimit_permission(tsk); | |
| + if (ret) { | |
| + rcu_read_unlock(); | |
| + return ret; | |
| + } | |
| + get_task_struct(tsk); | |
| + rcu_read_unlock(); | |
| + | |
| + ret = do_prlimit(tsk, resource, new_rlim ? &new : NULL, | |
| + old_rlim ? &old : NULL); | |
| + | |
| + if (!ret && old_rlim) { | |
| + rlim_to_rlim64(&old, &old64); | |
| + if (copy_to_user(old_rlim, &old64, sizeof(old64))) | |
| + ret = -EFAULT; | |
| + } | |
| + | |
| + put_task_struct(tsk); | |
| + return ret; | |
| +} | |
| + | |
| +SYSCALL_DEFINE2(setrlimit, unsigned int, resource, struct rlimit __user *, rlim) | |
| +{ | |
| + struct rlimit new_rlim; | |
| + | |
| + if (copy_from_user(&new_rlim, rlim, sizeof(*rlim))) | |
| + return -EFAULT; | |
| + return do_prlimit(current, resource, &new_rlim, NULL); | |
| } | |
| /* | |
| @@ -1557,11 +1587,8 @@ asmlinkage long sys_setrlimit(unsigned int resource, struct rlimit __user *rlim) | |
| * | |
| */ | |
| -static void accumulate_thread_rusage(struct task_struct *t, struct rusage *r, | |
| - cputime_t *utimep, cputime_t *stimep) | |
| +static void accumulate_thread_rusage(struct task_struct *t, struct rusage *r) | |
| { | |
| - *utimep = cputime_add(*utimep, t->utime); | |
| - *stimep = cputime_add(*stimep, t->stime); | |
| r->ru_nvcsw += t->nvcsw; | |
| r->ru_nivcsw += t->nivcsw; | |
| r->ru_minflt += t->min_flt; | |
| @@ -1574,13 +1601,16 @@ static void k_getrusage(struct task_struct *p, int who, struct rusage *r) | |
| { | |
| struct task_struct *t; | |
| unsigned long flags; | |
| - cputime_t utime, stime; | |
| + cputime_t tgutime, tgstime, utime, stime; | |
| + unsigned long maxrss = 0; | |
| memset((char *) r, 0, sizeof *r); | |
| utime = stime = cputime_zero; | |
| if (who == RUSAGE_THREAD) { | |
| - accumulate_thread_rusage(p, r, &utime, &stime); | |
| + task_times(current, &utime, &stime); | |
| + accumulate_thread_rusage(p, r); | |
| + maxrss = p->signal->maxrss; | |
| goto out; | |
| } | |
| @@ -1598,22 +1628,26 @@ static void k_getrusage(struct task_struct *p, int who, struct rusage *r) | |
| r->ru_majflt = p->signal->cmaj_flt; | |
| r->ru_inblock = p->signal->cinblock; | |
| r->ru_oublock = p->signal->coublock; | |
| + maxrss = p->signal->cmaxrss; | |
| if (who == RUSAGE_CHILDREN) | |
| break; | |
| case RUSAGE_SELF: | |
| - utime = cputime_add(utime, p->signal->utime); | |
| - stime = cputime_add(stime, p->signal->stime); | |
| + thread_group_times(p, &tgutime, &tgstime); | |
| + utime = cputime_add(utime, tgutime); | |
| + stime = cputime_add(stime, tgstime); | |
| r->ru_nvcsw += p->signal->nvcsw; | |
| r->ru_nivcsw += p->signal->nivcsw; | |
| r->ru_minflt += p->signal->min_flt; | |
| r->ru_majflt += p->signal->maj_flt; | |
| r->ru_inblock += p->signal->inblock; | |
| r->ru_oublock += p->signal->oublock; | |
| + if (maxrss < p->signal->maxrss) | |
| + maxrss = p->signal->maxrss; | |
| t = p; | |
| do { | |
| - accumulate_thread_rusage(t, r, &utime, &stime); | |
| + accumulate_thread_rusage(t, r); | |
| t = next_thread(t); | |
| } while (t != p); | |
| break; | |
| @@ -1626,6 +1660,15 @@ static void k_getrusage(struct task_struct *p, int who, struct rusage *r) | |
| out: | |
| cputime_to_timeval(utime, &r->ru_utime); | |
| cputime_to_timeval(stime, &r->ru_stime); | |
| + | |
| + if (who != RUSAGE_CHILDREN) { | |
| + struct mm_struct *mm = get_task_mm(p); | |
| + if (mm) { | |
| + setmax_mm_hiwater_rss(&maxrss, mm); | |
| + mmput(mm); | |
| + } | |
| + } | |
| + r->ru_maxrss = maxrss * (PAGE_SIZE / 1024); /* convert pages to KBs */ | |
| } | |
| int getrusage(struct task_struct *p, int who, struct rusage __user *ru) | |
| @@ -1635,7 +1678,7 @@ int getrusage(struct task_struct *p, int who, struct rusage __user *ru) | |
| return copy_to_user(ru, &r, sizeof(r)) ? -EFAULT : 0; | |
| } | |
| -asmlinkage long sys_getrusage(int who, struct rusage __user *ru) | |
| +SYSCALL_DEFINE2(getrusage, int, who, struct rusage __user *, ru) | |
| { | |
| if (who != RUSAGE_SELF && who != RUSAGE_CHILDREN && | |
| who != RUSAGE_THREAD) | |
| @@ -1643,59 +1686,65 @@ asmlinkage long sys_getrusage(int who, struct rusage __user *ru) | |
| return getrusage(current, who, ru); | |
| } | |
| -asmlinkage long sys_umask(int mask) | |
| +SYSCALL_DEFINE1(umask, int, mask) | |
| { | |
| mask = xchg(¤t->fs->umask, mask & S_IRWXUGO); | |
| return mask; | |
| } | |
| -asmlinkage long sys_prctl(int option, unsigned long arg2, unsigned long arg3, | |
| - unsigned long arg4, unsigned long arg5) | |
| +SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, | |
| + unsigned long, arg4, unsigned long, arg5) | |
| { | |
| - long error = 0; | |
| + struct task_struct *me = current; | |
| + unsigned char comm[sizeof(me->comm)]; | |
| + long error; | |
| - if (security_task_prctl(option, arg2, arg3, arg4, arg5, &error)) | |
| + error = security_task_prctl(option, arg2, arg3, arg4, arg5); | |
| + if (error != -ENOSYS) | |
| return error; | |
| + error = 0; | |
| switch (option) { | |
| case PR_SET_PDEATHSIG: | |
| if (!valid_signal(arg2)) { | |
| error = -EINVAL; | |
| break; | |
| } | |
| - current->pdeath_signal = arg2; | |
| + me->pdeath_signal = arg2; | |
| + error = 0; | |
| break; | |
| case PR_GET_PDEATHSIG: | |
| - error = put_user(current->pdeath_signal, (int __user *)arg2); | |
| + error = put_user(me->pdeath_signal, (int __user *)arg2); | |
| break; | |
| case PR_GET_DUMPABLE: | |
| - error = get_dumpable(current->mm); | |
| + error = get_dumpable(me->mm); | |
| break; | |
| case PR_SET_DUMPABLE: | |
| if (arg2 < 0 || arg2 > 1) { | |
| error = -EINVAL; | |
| break; | |
| } | |
| - set_dumpable(current->mm, arg2); | |
| + set_dumpable(me->mm, arg2); | |
| + error = 0; | |
| break; | |
| case PR_SET_UNALIGN: | |
| - error = SET_UNALIGN_CTL(current, arg2); | |
| + error = SET_UNALIGN_CTL(me, arg2); | |
| break; | |
| case PR_GET_UNALIGN: | |
| - error = GET_UNALIGN_CTL(current, arg2); | |
| + error = GET_UNALIGN_CTL(me, arg2); | |
| break; | |
| case PR_SET_FPEMU: | |
| - error = SET_FPEMU_CTL(current, arg2); | |
| + error = SET_FPEMU_CTL(me, arg2); | |
| break; | |
| case PR_GET_FPEMU: | |
| - error = GET_FPEMU_CTL(current, arg2); | |
| + error = GET_FPEMU_CTL(me, arg2); | |
| break; | |
| case PR_SET_FPEXC: | |
| - error = SET_FPEXC_CTL(current, arg2); | |
| + error = SET_FPEXC_CTL(me, arg2); | |
| break; | |
| case PR_GET_FPEXC: | |
| - error = GET_FPEXC_CTL(current, arg2); | |
| + error = GET_FPEXC_CTL(me, arg2); | |
| break; | |
| case PR_GET_TIMING: | |
| error = PR_TIMING_STATISTICAL; | |
| @@ -1703,33 +1752,29 @@ asmlinkage long sys_prctl(int option, unsigned long arg2, unsigned long arg3, | |
| case PR_SET_TIMING: | |
| if (arg2 != PR_TIMING_STATISTICAL) | |
| error = -EINVAL; | |
| + else | |
| + error = 0; | |
| break; | |
| - case PR_SET_NAME: { | |
| - struct task_struct *me = current; | |
| - unsigned char ncomm[sizeof(me->comm)]; | |
| - | |
| - ncomm[sizeof(me->comm)-1] = 0; | |
| - if (strncpy_from_user(ncomm, (char __user *)arg2, | |
| - sizeof(me->comm)-1) < 0) | |
| + case PR_SET_NAME: | |
| + comm[sizeof(me->comm)-1] = 0; | |
| + if (strncpy_from_user(comm, (char __user *)arg2, | |
| + sizeof(me->comm) - 1) < 0) | |
| return -EFAULT; | |
| - set_task_comm(me, ncomm); | |
| + set_task_comm(me, comm); | |
| + proc_comm_connector(me); | |
| return 0; | |
| - } | |
| - case PR_GET_NAME: { | |
| - struct task_struct *me = current; | |
| - unsigned char tcomm[sizeof(me->comm)]; | |
| - | |
| - get_task_comm(tcomm, me); | |
| - if (copy_to_user((char __user *)arg2, tcomm, sizeof(tcomm))) | |
| + case PR_GET_NAME: | |
| + get_task_comm(comm, me); | |
| + if (copy_to_user((char __user *)arg2, comm, | |
| + sizeof(comm))) | |
| return -EFAULT; | |
| return 0; | |
| - } | |
| case PR_GET_ENDIAN: | |
| - error = GET_ENDIAN(current, arg2); | |
| + error = GET_ENDIAN(me, arg2); | |
| break; | |
| case PR_SET_ENDIAN: | |
| - error = SET_ENDIAN(current, arg2); | |
| + error = SET_ENDIAN(me, arg2); | |
| break; | |
| case PR_GET_SECCOMP: | |
| @@ -1744,6 +1789,58 @@ asmlinkage long sys_prctl(int option, unsigned long arg2, unsigned long arg3, | |
| case PR_SET_TSC: | |
| error = SET_TSC_CTL(arg2); | |
| break; | |
| + case PR_TASK_PERF_EVENTS_DISABLE: | |
| + error = perf_event_task_disable(); | |
| + break; | |
| + case PR_TASK_PERF_EVENTS_ENABLE: | |
| + error = perf_event_task_enable(); | |
| + break; | |
| + case PR_GET_TIMERSLACK: | |
| + error = current->timer_slack_ns; | |
| + break; | |
| + case PR_SET_TIMERSLACK: | |
| + if (arg2 <= 0) | |
| + current->timer_slack_ns = | |
| + current->default_timer_slack_ns; | |
| + else | |
| + current->timer_slack_ns = arg2; | |
| + error = 0; | |
| + break; | |
| + case PR_MCE_KILL: | |
| + if (arg4 | arg5) | |
| + return -EINVAL; | |
| + switch (arg2) { | |
| + case PR_MCE_KILL_CLEAR: | |
| + if (arg3 != 0) | |
| + return -EINVAL; | |
| + current->flags &= ~PF_MCE_PROCESS; | |
| + break; | |
| + case PR_MCE_KILL_SET: | |
| + current->flags |= PF_MCE_PROCESS; | |
| + if (arg3 == PR_MCE_KILL_EARLY) | |
| + current->flags |= PF_MCE_EARLY; | |
| + else if (arg3 == PR_MCE_KILL_LATE) | |
| + current->flags &= ~PF_MCE_EARLY; | |
| + else if (arg3 == PR_MCE_KILL_DEFAULT) | |
| + current->flags &= | |
| + ~(PF_MCE_EARLY|PF_MCE_PROCESS); | |
| + else | |
| + return -EINVAL; | |
| + break; | |
| + default: | |
| + return -EINVAL; | |
| + } | |
| + error = 0; | |
| + break; | |
| + case PR_MCE_KILL_GET: | |
| + if (arg2 | arg3 | arg4 | arg5) | |
| + return -EINVAL; | |
| + if (current->flags & PF_MCE_PROCESS) | |
| + error = (current->flags & PF_MCE_EARLY) ? | |
| + PR_MCE_KILL_EARLY : PR_MCE_KILL_LATE; | |
| + else | |
| + error = PR_MCE_KILL_DEFAULT; | |
| + break; | |
| default: | |
| error = -EINVAL; | |
| break; | |
| @@ -1751,8 +1848,8 @@ asmlinkage long sys_prctl(int option, unsigned long arg2, unsigned long arg3, | |
| return error; | |
| } | |
| -asmlinkage long sys_getcpu(unsigned __user *cpup, unsigned __user *nodep, | |
| - struct getcpu_cache __user *unused) | |
| +SYSCALL_DEFINE3(getcpu, unsigned __user *, cpup, unsigned __user *, nodep, | |
| + struct getcpu_cache __user *, unused) | |
| { | |
| int err = 0; | |
| int cpu = raw_smp_processor_id(); | |
| @@ -1765,9 +1862,9 @@ asmlinkage long sys_getcpu(unsigned __user *cpup, unsigned __user *nodep, | |
| char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff"; | |
| -static void argv_cleanup(char **argv, char **envp) | |
| +static void argv_cleanup(struct subprocess_info *info) | |
| { | |
| - argv_free(argv); | |
| + argv_free(info->argv); | |
| } | |
| /** | |
| @@ -1795,13 +1892,13 @@ int orderly_poweroff(bool force) | |
| goto out; | |
| } | |
| - info = call_usermodehelper_setup(argv[0], argv, envp); | |
| + info = call_usermodehelper_setup(argv[0], argv, envp, GFP_ATOMIC); | |
| if (info == NULL) { | |
| argv_free(argv); | |
| goto out; | |
| } | |
| - call_usermodehelper_setcleanup(info, argv_cleanup); | |
| + call_usermodehelper_setfns(info, NULL, argv_cleanup, NULL); | |
| ret = call_usermodehelper_exec(info, UMH_NO_WAIT); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment