summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Rothwell <sfr@canb.auug.org.au>2010-02-11 16:44:08 +1100
committerStephen Rothwell <sfr@canb.auug.org.au>2010-02-11 16:44:08 +1100
commit92828ce7f9e73b7ab917f7a06774a6b89eedc6ab (patch)
tree22022c7b833030c501f78d189453eb12093d8110
parent966e506a5840148411f6b1d66a391eebed6e4dc4 (diff)
parent57158a0fd4c9e29b403d422ac820cf238066cedf (diff)
Merge remote branch 'limits/writable_limits'
Conflicts: arch/x86/ia32/ia32entry.S arch/x86/include/asm/unistd_32.h arch/x86/include/asm/unistd_64.h arch/x86/kernel/syscall_table_32.S
-rw-r--r--Documentation/filesystems/proc.txt11
-rw-r--r--arch/x86/ia32/ia32entry.S2
-rw-r--r--arch/x86/include/asm/unistd_32.h4
-rw-r--r--arch/x86/include/asm/unistd_64.h4
-rw-r--r--arch/x86/kernel/syscall_table_32.S2
-rw-r--r--fs/proc/base.c101
-rw-r--r--include/asm-generic/unistd.h6
-rw-r--r--include/linux/posix-timers.h2
-rw-r--r--include/linux/resource.h2
-rw-r--r--include/linux/security.h9
-rw-r--r--include/linux/syscalls.h4
-rw-r--r--kernel/compat.c98
-rw-r--r--kernel/posix-cpu-timers.c10
-rw-r--r--kernel/sys.c151
-rw-r--r--security/capability.c3
-rw-r--r--security/security.c5
-rw-r--r--security/selinux/hooks.c10
17 files changed, 346 insertions, 78 deletions
diff --git a/Documentation/filesystems/proc.txt b/Documentation/filesystems/proc.txt
index 0d07513a67a6..1beb617fae2d 100644
--- a/Documentation/filesystems/proc.txt
+++ b/Documentation/filesystems/proc.txt
@@ -39,6 +39,7 @@ Table of Contents
3.4 /proc/<pid>/coredump_filter - Core dump filtering settings
3.5 /proc/<pid>/mountinfo - Information about mounts
3.6 /proc/<pid>/comm & /proc/<pid>/task/<tid>/comm
+ 3.7 /proc/<pid>/limits & /proc/<pid>/task/<tid>/limits
------------------------------------------------------------------------------
@@ -1416,3 +1417,13 @@ a task to set its own or one of its thread siblings comm value. The comm value
is limited in size compared to the cmdline value, so writing anything longer
then the kernel's TASK_COMM_LEN (currently 16 chars) will result in a truncated
comm value.
+
+3.7 /proc/<pid>/limits & /proc/<pid>/task/<tid>/limits
+----------------------------------------------------------
+They serve as resource limits information providers. Each limit is printed out
+on a single line with its soft and hard quota. Additionally the limits may be
+changed (with sufficient privileges) by writing a string of format:
+<limit name>=<soft_limit>:<hard_limit>
+into that file. Both soft and hard limits there may be "undefined" keyword.
+Note that the value cannot be arbitraty. The same rules as for setlimit
+syscall hold.
diff --git a/arch/x86/ia32/ia32entry.S b/arch/x86/ia32/ia32entry.S
index 9f5aaee4a2c9..a861829fb396 100644
--- a/arch/x86/ia32/ia32entry.S
+++ b/arch/x86/ia32/ia32entry.S
@@ -844,4 +844,6 @@ ia32_sys_call_table:
.quad compat_sys_recvmmsg
.quad sys_fanotify_init
.quad sys32_fanotify_mark
+ .quad compat_sys_getprlimit /* 340 */
+ .quad compat_sys_setprlimit
ia32_syscall_end:
diff --git a/arch/x86/include/asm/unistd_32.h b/arch/x86/include/asm/unistd_32.h
index cf70e9e17dbc..64c7765ea4a1 100644
--- a/arch/x86/include/asm/unistd_32.h
+++ b/arch/x86/include/asm/unistd_32.h
@@ -345,10 +345,12 @@
#define __NR_recvmmsg 337
#define __NR_fanotify_init 338
#define __NR_fanotify_mark 339
+#define __NR_getprlimit 340
+#define __NR_setprlimit 341
#ifdef __KERNEL__
-#define NR_syscalls 340
+#define NR_syscalls 342
#define __ARCH_WANT_IPC_PARSE_VERSION
#define __ARCH_WANT_OLD_READDIR
diff --git a/arch/x86/include/asm/unistd_64.h b/arch/x86/include/asm/unistd_64.h
index 7fbd20f943f6..78e2e0bf1394 100644
--- a/arch/x86/include/asm/unistd_64.h
+++ b/arch/x86/include/asm/unistd_64.h
@@ -667,6 +667,10 @@ __SYSCALL(__NR_recvmmsg, sys_recvmmsg)
__SYSCALL(__NR_fanotify_init, sys_fanotify_init)
#define __NR_fanotify_mark 301
__SYSCALL(__NR_fanotify_mark, sys_fanotify_mark)
+#define __NR_getprlimit 302
+__SYSCALL(__NR_getprlimit, sys_getprlimit)
+#define __NR_setprlimit 303
+__SYSCALL(__NR_setprlimit, sys_setprlimit)
#ifndef __NO_STUBS
#define __ARCH_WANT_OLD_READDIR
diff --git a/arch/x86/kernel/syscall_table_32.S b/arch/x86/kernel/syscall_table_32.S
index 8e3f05add40b..817ddc50961f 100644
--- a/arch/x86/kernel/syscall_table_32.S
+++ b/arch/x86/kernel/syscall_table_32.S
@@ -339,3 +339,5 @@ ENTRY(sys_call_table)
.long sys_recvmmsg
.long sys_fanotify_init
.long sys_fanotify_mark
+ .long sys_getprlimit /* 340 */
+ .long sys_setprlimit
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 746895ddfda1..6f167e24f871 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -477,19 +477,30 @@ static const struct limit_names lnames[RLIM_NLIMITS] = {
};
/* Display limits for a process */
-static int proc_pid_limits(struct task_struct *task, char *buffer)
+static ssize_t limits_read(struct file *file, char __user *buf, size_t rcount,
+ loff_t *ppos)
{
- unsigned int i;
- int count = 0;
- unsigned long flags;
- char *bufptr = buffer;
-
struct rlimit rlim[RLIM_NLIMITS];
+ struct task_struct *task;
+ unsigned long flags;
+ unsigned int i;
+ ssize_t count = 0;
+ char *bufptr;
- if (!lock_task_sighand(task, &flags))
+ task = get_proc_task(file->f_path.dentry->d_inode);
+ if (!task)
+ return -ESRCH;
+ if (!lock_task_sighand(task, &flags)) {
+ put_task_struct(task);
return 0;
+ }
memcpy(rlim, task->signal->rlim, sizeof(struct rlimit) * RLIM_NLIMITS);
unlock_task_sighand(task, &flags);
+ put_task_struct(task);
+
+ bufptr = (char *)__get_free_page(GFP_TEMPORARY);
+ if (!bufptr)
+ return -ENOMEM;
/*
* print the file header
@@ -518,9 +529,81 @@ static int proc_pid_limits(struct task_struct *task, char *buffer)
count += sprintf(&bufptr[count], "\n");
}
+ count = simple_read_from_buffer(buf, rcount, ppos, bufptr, count);
+
+ free_page((unsigned long)bufptr);
+
+ return count;
+}
+
+static ssize_t limits_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode);
+ char str[32 + 1 + 16 + 1 + 16 + 1], *delim, *next;
+ struct rlimit new_rlimit;
+ unsigned int i;
+ int ret;
+
+ if (!task) {
+ count = -ESRCH;
+ goto out;
+ }
+ if (copy_from_user(str, buf, min(count, sizeof(str) - 1))) {
+ count = -EFAULT;
+ goto put_task;
+ }
+
+ str[min(count, sizeof(str) - 1)] = 0;
+
+ delim = strchr(str, '=');
+ if (!delim) {
+ count = -EINVAL;
+ goto put_task;
+ }
+ *delim++ = 0; /* for easy 'str' usage */
+ new_rlimit.rlim_cur = simple_strtoul(delim, &next, 0);
+ if (*next != ':') {
+ if (strncmp(delim, "unlimited:", 10)) {
+ count = -EINVAL;
+ goto put_task;
+ }
+ new_rlimit.rlim_cur = RLIM_INFINITY;
+ next = delim + 9; /* move to ':' */
+ }
+ delim = next + 1;
+ new_rlimit.rlim_max = simple_strtoul(delim, &next, 0);
+ if (*next != 0) {
+ if (strcmp(delim, "unlimited")) {
+ count = -EINVAL;
+ goto put_task;
+ }
+ new_rlimit.rlim_max = RLIM_INFINITY;
+ }
+
+ for (i = 0; i < RLIM_NLIMITS; i++)
+ if (!strcmp(str, lnames[i].name))
+ break;
+ if (i >= RLIM_NLIMITS) {
+ count = -EINVAL;
+ goto put_task;
+ }
+
+ ret = do_setrlimit(task, i, &new_rlimit);
+ if (ret)
+ count = ret;
+
+put_task:
+ put_task_struct(task);
+out:
return count;
}
+static const struct file_operations proc_pid_limits_operations = {
+ .read = limits_read,
+ .write = limits_write,
+};
+
#ifdef CONFIG_HAVE_ARCH_TRACEHOOK
static int proc_pid_syscall(struct task_struct *task, char *buffer)
{
@@ -2573,7 +2656,7 @@ static const struct pid_entry tgid_base_stuff[] = {
INF("auxv", S_IRUSR, proc_pid_auxv),
ONE("status", S_IRUGO, proc_pid_status),
ONE("personality", S_IRUSR, proc_pid_personality),
- INF("limits", S_IRUSR, proc_pid_limits),
+ REG("limits", S_IRUSR|S_IWUSR, proc_pid_limits_operations),
#ifdef CONFIG_SCHED_DEBUG
REG("sched", S_IRUGO|S_IWUSR, proc_pid_sched_operations),
#endif
@@ -2908,7 +2991,7 @@ static const struct pid_entry tid_base_stuff[] = {
INF("auxv", S_IRUSR, proc_pid_auxv),
ONE("status", S_IRUGO, proc_pid_status),
ONE("personality", S_IRUSR, proc_pid_personality),
- INF("limits", S_IRUSR, proc_pid_limits),
+ REG("limits", S_IRUSR|S_IWUSR, proc_pid_limits_operations),
#ifdef CONFIG_SCHED_DEBUG
REG("sched", S_IRUGO|S_IWUSR, proc_pid_sched_operations),
#endif
diff --git a/include/asm-generic/unistd.h b/include/asm-generic/unistd.h
index 6a0b30f78a62..fa0156f25415 100644
--- a/include/asm-generic/unistd.h
+++ b/include/asm-generic/unistd.h
@@ -626,9 +626,13 @@ __SYSCALL(__NR_perf_event_open, sys_perf_event_open)
__SYSCALL(__NR_accept4, sys_accept4)
#define __NR_recvmmsg 243
__SYSCALL(__NR_recvmmsg, sys_recvmmsg)
+#define __NR_getprlimit 244
+__SYSCALL(__NR_getprlimit, sys_getprlimit)
+#define __NR_setprlimit 245
+__SYSCALL(__NR_setprlimit, sys_setprlimit)
#undef __NR_syscalls
-#define __NR_syscalls 244
+#define __NR_syscalls 246
/*
* All syscalls below here should go away really,
diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h
index 4f71bf4e628c..3e23844a6990 100644
--- a/include/linux/posix-timers.h
+++ b/include/linux/posix-timers.h
@@ -117,6 +117,6 @@ void set_process_cpu_timer(struct task_struct *task, unsigned int clock_idx,
long clock_nanosleep_restart(struct restart_block *restart_block);
-void update_rlimit_cpu(unsigned long rlim_new);
+void update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new);
#endif
diff --git a/include/linux/resource.h b/include/linux/resource.h
index f1e914eefeab..cf8dc96653ee 100644
--- a/include/linux/resource.h
+++ b/include/linux/resource.h
@@ -73,6 +73,8 @@ struct rlimit {
struct task_struct;
int getrusage(struct task_struct *p, int who, struct rusage __user *ru);
+int do_setrlimit(struct task_struct *tsk, unsigned int resource,
+ struct rlimit *new_rlim);
#endif /* __KERNEL__ */
diff --git a/include/linux/security.h b/include/linux/security.h
index d5973638f244..ba3b3da5525b 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -1601,7 +1601,8 @@ struct security_operations {
int (*task_setnice) (struct task_struct *p, int nice);
int (*task_setioprio) (struct task_struct *p, int ioprio);
int (*task_getioprio) (struct task_struct *p);
- int (*task_setrlimit) (unsigned int resource, struct rlimit *new_rlim);
+ int (*task_setrlimit) (struct task_struct *p, unsigned int resource,
+ struct rlimit *new_rlim);
int (*task_setscheduler) (struct task_struct *p, int policy,
struct sched_param *lp);
int (*task_getscheduler) (struct task_struct *p);
@@ -1866,7 +1867,8 @@ int security_task_setgroups(struct group_info *group_info);
int security_task_setnice(struct task_struct *p, int nice);
int security_task_setioprio(struct task_struct *p, int ioprio);
int security_task_getioprio(struct task_struct *p);
-int security_task_setrlimit(unsigned int resource, struct rlimit *new_rlim);
+int security_task_setrlimit(struct task_struct *p, unsigned int resource,
+ struct rlimit *new_rlim);
int security_task_setscheduler(struct task_struct *p,
int policy, struct sched_param *lp);
int security_task_getscheduler(struct task_struct *p);
@@ -2482,7 +2484,8 @@ static inline int security_task_getioprio(struct task_struct *p)
return 0;
}
-static inline int security_task_setrlimit(unsigned int resource,
+static inline int security_task_setrlimit(struct task_struct *p,
+ unsigned int resource,
struct rlimit *new_rlim)
{
return 0;
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index dbea1daf143a..e4b90e16ee1b 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -655,11 +655,15 @@ asmlinkage long sys_newuname(struct new_utsname __user *name);
asmlinkage long sys_getrlimit(unsigned int resource,
struct rlimit __user *rlim);
+asmlinkage long sys_getprlimit(pid_t pid, unsigned int resource,
+ struct rlimit __user *rlim);
#if defined(COMPAT_RLIM_OLD_INFINITY) || !(defined(CONFIG_IA64))
asmlinkage long sys_old_getrlimit(unsigned int resource, struct rlimit __user *rlim);
#endif
asmlinkage long sys_setrlimit(unsigned int resource,
struct rlimit __user *rlim);
+asmlinkage long sys_setprlimit(pid_t pid, unsigned int resource,
+ struct rlimit __user *rlim);
asmlinkage long sys_getrusage(int who, struct rusage __user *ru);
asmlinkage long sys_umask(int mask);
diff --git a/kernel/compat.c b/kernel/compat.c
index f6c204f07ea6..c820552d0dc4 100644
--- a/kernel/compat.c
+++ b/kernel/compat.c
@@ -274,29 +274,50 @@ asmlinkage long compat_sys_sigprocmask(int how, compat_old_sigset_t __user *set,
return ret;
}
+static int get_compat_rlimit(struct rlimit *dst,
+ const struct compat_rlimit __user *src)
+{
+ if (!access_ok(VERIFY_READ, src, sizeof(*src)) ||
+ __get_user(dst->rlim_cur, &src->rlim_cur) ||
+ __get_user(dst->rlim_max, &src->rlim_max))
+ return -EFAULT;
+
+ if (dst->rlim_cur == COMPAT_RLIM_INFINITY)
+ dst->rlim_cur = RLIM_INFINITY;
+ if (dst->rlim_max == COMPAT_RLIM_INFINITY)
+ dst->rlim_max = RLIM_INFINITY;
+ return 0;
+}
+
+static int put_compat_rlimit(const struct rlimit *src,
+ struct compat_rlimit __user *dst)
+{
+ struct rlimit r = *src;
+
+ if (r.rlim_cur > COMPAT_RLIM_INFINITY)
+ r.rlim_cur = COMPAT_RLIM_INFINITY;
+ if (r.rlim_max > COMPAT_RLIM_INFINITY)
+ r.rlim_max = COMPAT_RLIM_INFINITY;
+
+ if (!access_ok(VERIFY_WRITE, dst, sizeof(*dst)) ||
+ __put_user(r.rlim_cur, &dst->rlim_cur) ||
+ __put_user(r.rlim_max, &dst->rlim_max))
+ return -EFAULT;
+
+ return 0;
+}
+
asmlinkage long compat_sys_setrlimit(unsigned int resource,
struct compat_rlimit __user *rlim)
{
struct rlimit r;
int ret;
- mm_segment_t old_fs = get_fs ();
- if (resource >= RLIM_NLIMITS)
- return -EINVAL;
-
- if (!access_ok(VERIFY_READ, rlim, sizeof(*rlim)) ||
- __get_user(r.rlim_cur, &rlim->rlim_cur) ||
- __get_user(r.rlim_max, &rlim->rlim_max))
- return -EFAULT;
+ ret = get_compat_rlimit(&r, rlim);
+ if (ret)
+ return ret;
- if (r.rlim_cur == COMPAT_RLIM_INFINITY)
- r.rlim_cur = RLIM_INFINITY;
- if (r.rlim_max == COMPAT_RLIM_INFINITY)
- r.rlim_max = RLIM_INFINITY;
- set_fs(KERNEL_DS);
- ret = sys_setrlimit(resource, (struct rlimit __user *) &r);
- set_fs(old_fs);
- return ret;
+ return do_setrlimit(current, resource, &r);
}
#ifdef COMPAT_RLIM_OLD_INFINITY
@@ -336,19 +357,42 @@ asmlinkage long compat_sys_getrlimit (unsigned int resource,
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
- ret = sys_getrlimit(resource, (struct rlimit __user *) &r);
+ ret = sys_getrlimit(resource, (struct rlimit __force __user *)&r);
set_fs(old_fs);
- if (!ret) {
- if (r.rlim_cur > COMPAT_RLIM_INFINITY)
- r.rlim_cur = COMPAT_RLIM_INFINITY;
- if (r.rlim_max > COMPAT_RLIM_INFINITY)
- r.rlim_max = COMPAT_RLIM_INFINITY;
+ if (!ret)
+ ret = put_compat_rlimit(&r, rlim);
+ return ret;
+}
- if (!access_ok(VERIFY_WRITE, rlim, sizeof(*rlim)) ||
- __put_user(r.rlim_cur, &rlim->rlim_cur) ||
- __put_user(r.rlim_max, &rlim->rlim_max))
- return -EFAULT;
- }
+asmlinkage long compat_sys_setprlimit(pid_t pid, unsigned int resource,
+ struct compat_rlimit __user *rlim)
+{
+ mm_segment_t old_fs = get_fs();
+ struct rlimit r;
+ int ret;
+
+ ret = get_compat_rlimit(&r, rlim);
+ if (ret)
+ return ret;
+
+ set_fs(KERNEL_DS);
+ ret = sys_setprlimit(pid, resource, (struct rlimit __force __user *)&r);
+ set_fs(old_fs);
+ return ret;
+}
+
+asmlinkage long compat_sys_getprlimit(pid_t pid, unsigned int resource,
+ struct compat_rlimit __user *rlim)
+{
+ mm_segment_t old_fs = get_fs();
+ struct rlimit r;
+ int ret;
+
+ set_fs(KERNEL_DS);
+ ret = sys_getprlimit(pid, resource, (struct rlimit __force __user *)&r);
+ set_fs(old_fs);
+ if (!ret)
+ ret = put_compat_rlimit(&r, rlim);
return ret;
}
diff --git a/kernel/posix-cpu-timers.c b/kernel/posix-cpu-timers.c
index 438ff4523513..d8e543cfeddf 100644
--- a/kernel/posix-cpu-timers.c
+++ b/kernel/posix-cpu-timers.c
@@ -13,16 +13,16 @@
/*
* Called after updating RLIMIT_CPU to set timer expiration if necessary.
*/
-void update_rlimit_cpu(unsigned long rlim_new)
+void update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new)
{
cputime_t cputime = secs_to_cputime(rlim_new);
- struct signal_struct *const sig = current->signal;
+ struct signal_struct *const sig = task->signal;
if (cputime_eq(sig->it[CPUCLOCK_PROF].expires, cputime_zero) ||
cputime_gt(sig->it[CPUCLOCK_PROF].expires, cputime)) {
- spin_lock_irq(&current->sighand->siglock);
- set_process_cpu_timer(current, CPUCLOCK_PROF, &cputime, NULL);
- spin_unlock_irq(&current->sighand->siglock);
+ spin_lock_irq(&task->sighand->siglock);
+ set_process_cpu_timer(task, CPUCLOCK_PROF, &cputime, NULL);
+ spin_unlock_irq(&task->sighand->siglock);
}
}
diff --git a/kernel/sys.c b/kernel/sys.c
index 26a6b73a6b85..c801a55c169f 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -1214,6 +1214,61 @@ SYSCALL_DEFINE2(getrlimit, unsigned int, resource, struct rlimit __user *, rlim)
}
}
+static int check_prlimit_permission(struct task_struct *task)
+{
+ const struct cred *cred = current_cred(), *tcred;
+ int ret = 0;
+
+ rcu_read_lock();
+ tcred = __task_cred(task);
+ if ((cred->uid != tcred->euid ||
+ cred->uid != tcred->suid ||
+ cred->uid != tcred->uid ||
+ cred->gid != tcred->egid ||
+ cred->gid != tcred->sgid ||
+ cred->gid != tcred->gid) &&
+ !capable(CAP_SYS_RESOURCE)) {
+ ret = -EPERM;
+ }
+ rcu_read_unlock();
+ return ret;
+}
+
+SYSCALL_DEFINE3(getprlimit, pid_t, pid, unsigned int, resource,
+ struct rlimit __user *, rlim)
+{
+ struct rlimit val;
+ struct task_struct *tsk;
+ int ret;
+
+ if (resource >= RLIM_NLIMITS)
+ return -EINVAL;
+
+ read_lock(&tasklist_lock);
+
+ tsk = find_task_by_vpid(pid);
+ if (!tsk || !tsk->sighand) {
+ ret = -ESRCH;
+ goto err_unlock;
+ }
+
+ ret = check_prlimit_permission(tsk);
+ if (ret)
+ goto err_unlock;
+
+ task_lock(tsk->group_leader);
+ val = tsk->signal->rlim[resource];
+ task_unlock(tsk->group_leader);
+
+ read_unlock(&tasklist_lock);
+
+ return copy_to_user(rlim, &val, sizeof(*rlim)) ? -EFAULT : 0;
+err_unlock:
+ read_unlock(&tasklist_lock);
+ return ret;
+}
+
+
#ifdef __ARCH_WANT_SYS_OLD_GETRLIMIT
/*
@@ -1239,43 +1294,52 @@ SYSCALL_DEFINE2(old_getrlimit, unsigned int, resource,
#endif
-SYSCALL_DEFINE2(setrlimit, unsigned int, resource, struct rlimit __user *, rlim)
+/* make sure you are allowed to change @tsk limits before calling this */
+int do_setrlimit(struct task_struct *tsk, unsigned int resource,
+ struct rlimit *new_rlim)
{
- struct rlimit new_rlim, *old_rlim;
- int retval;
+ struct rlimit *old_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)
+ 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)
+ if (resource == RLIMIT_NOFILE && new_rlim->rlim_max > sysctl_nr_open)
return -EPERM;
- retval = security_task_setrlimit(resource, &new_rlim);
- if (retval)
- return retval;
+ /* optimization: 'current' doesn't need locking, e.g. setrlimit */
+ if (tsk != current) {
+ /* protect tsk->signal and tsk->sighand from disappearing */
+ read_lock(&tasklist_lock);
+ if (!tsk->sighand) {
+ retval = -ESRCH;
+ goto out;
+ }
+ }
- if (resource == RLIMIT_CPU && new_rlim.rlim_cur == 0) {
+ 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;
+ new_rlim->rlim_cur = 1;
}
- task_lock(current->group_leader);
- *old_rlim = new_rlim;
- task_unlock(current->group_leader);
-
- if (resource != RLIMIT_CPU)
+ old_rlim = tsk->signal->rlim + resource;
+ task_lock(tsk->group_leader);
+ if ((new_rlim->rlim_max > old_rlim->rlim_max) &&
+ !capable(CAP_SYS_RESOURCE))
+ retval = -EPERM;
+ if (!retval)
+ retval = security_task_setrlimit(tsk, resource, new_rlim);
+ if (!retval)
+ *old_rlim = *new_rlim;
+ task_unlock(tsk->group_leader);
+
+ if (retval || resource != RLIMIT_CPU)
goto out;
/*
@@ -1284,12 +1348,51 @@ SYSCALL_DEFINE2(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)
+ if (new_rlim->rlim_cur == RLIM_INFINITY)
goto out;
- update_rlimit_cpu(new_rlim.rlim_cur);
+ update_rlimit_cpu(tsk, new_rlim->rlim_cur);
out:
- return 0;
+ if (tsk != current)
+ read_unlock(&tasklist_lock);
+ return retval;
+}
+
+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_setrlimit(current, resource, &new_rlim);
+}
+
+SYSCALL_DEFINE3(setprlimit, pid_t, pid, unsigned int, resource,
+ struct rlimit __user *, rlim)
+{
+ struct task_struct *tsk;
+ struct rlimit new_rlim;
+ int ret;
+
+ if (copy_from_user(&new_rlim, rlim, sizeof(*rlim)))
+ return -EFAULT;
+
+ rcu_read_lock();
+ tsk = find_task_by_vpid(pid);
+ if (!tsk) {
+ rcu_read_unlock();
+ return -ESRCH;
+ }
+ get_task_struct(tsk);
+ rcu_read_unlock();
+
+ ret = check_prlimit_permission(tsk);
+ if (!ret)
+ ret = do_setrlimit(tsk, resource, &new_rlim);
+
+ put_task_struct(tsk);
+
+ return ret;
}
/*
diff --git a/security/capability.c b/security/capability.c
index 5c700e1a4fd3..10f23a4c84e5 100644
--- a/security/capability.c
+++ b/security/capability.c
@@ -466,7 +466,8 @@ static int cap_task_getioprio(struct task_struct *p)
return 0;
}
-static int cap_task_setrlimit(unsigned int resource, struct rlimit *new_rlim)
+static int cap_task_setrlimit(struct task_struct *p, unsigned int resource,
+ struct rlimit *new_rlim)
{
return 0;
}
diff --git a/security/security.c b/security/security.c
index a433392bbe20..801972467a70 100644
--- a/security/security.c
+++ b/security/security.c
@@ -836,9 +836,10 @@ int security_task_getioprio(struct task_struct *p)
return security_ops->task_getioprio(p);
}
-int security_task_setrlimit(unsigned int resource, struct rlimit *new_rlim)
+int security_task_setrlimit(struct task_struct *p, unsigned int resource,
+ struct rlimit *new_rlim)
{
- return security_ops->task_setrlimit(resource, new_rlim);
+ return security_ops->task_setrlimit(p, resource, new_rlim);
}
int security_task_setscheduler(struct task_struct *p,
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 6b36ce2eef2e..6051e6004c96 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -2367,7 +2367,8 @@ static void selinux_bprm_committing_creds(struct linux_binprm *bprm)
initrlim = init_task.signal->rlim + i;
rlim->rlim_cur = min(rlim->rlim_max, initrlim->rlim_cur);
}
- update_rlimit_cpu(current->signal->rlim[RLIMIT_CPU].rlim_cur);
+ update_rlimit_cpu(current,
+ current->signal->rlim[RLIMIT_CPU].rlim_cur);
}
}
@@ -3400,16 +3401,17 @@ static int selinux_task_getioprio(struct task_struct *p)
return current_has_perm(p, PROCESS__GETSCHED);
}
-static int selinux_task_setrlimit(unsigned int resource, struct rlimit *new_rlim)
+static int selinux_task_setrlimit(struct task_struct *p, unsigned int resource,
+ struct rlimit *new_rlim)
{
- struct rlimit *old_rlim = current->signal->rlim + resource;
+ struct rlimit *old_rlim = p->signal->rlim + resource;
/* Control the ability to change the hard limit (whether
lowering or raising it), so that the hard limit can
later be used as a safe reset point for the soft limit
upon context transitions. See selinux_bprm_committing_creds. */
if (old_rlim->rlim_max != new_rlim->rlim_max)
- return current_has_perm(current, PROCESS__SETRLIMIT);
+ return current_has_perm(p, PROCESS__SETRLIMIT);
return 0;
}