2005-01-18 20:45:24

by Bill Rugolsky Jr.

[permalink] [raw]
Subject: [RFC][PATCH] /proc/<pid>/rlimit

This patch against 2.6.11-rc1-bk6 adds /proc/<pid>/rlimit to export
per-process resource limit settings. It was written to help analyze
daemon core dump size settings, but may be more generally useful.
Tested on 2.6.10. Sample output:

root@tiny ~ # cat /proc/$$/rlimit
cpu unlimited unlimited
fsize unlimited unlimited
data unlimited unlimited
stack 8388608 unlimited
core 0 unlimited
rss unlimited unlimited
nproc 111 111
nofile 1024 1024
memlock 32768 32768
as unlimited unlimited
locks unlimited unlimited
sigpending 1024 1024
msgqueue 819200 819200

Feedback welcome.

Signed-off-by: Bill Rugolsky <[email protected]>

--- linux-2.6.11-rc1-bk6/fs/proc/base.c.rlimit 2005-01-18 15:01:10.120960254 -0500
+++ linux-2.6.11-rc1-bk6/fs/proc/base.c 2005-01-18 15:07:28.102661832 -0500
@@ -32,6 +32,7 @@
#include <linux/mount.h>
#include <linux/security.h>
#include <linux/ptrace.h>
+#include <linux/resource.h>
#include "internal.h"

/*
@@ -61,6 +62,7 @@
PROC_TGID_MAPS,
PROC_TGID_MOUNTS,
PROC_TGID_WCHAN,
+ PROC_TGID_RLIMIT,
#ifdef CONFIG_SCHEDSTATS
PROC_TGID_SCHEDSTAT,
#endif
@@ -87,6 +89,7 @@
PROC_TID_MAPS,
PROC_TID_MOUNTS,
PROC_TID_WCHAN,
+ PROC_TID_RLIMIT,
#ifdef CONFIG_SCHEDSTATS
PROC_TID_SCHEDSTAT,
#endif
@@ -124,6 +127,7 @@
E(PROC_TGID_ROOT, "root", S_IFLNK|S_IRWXUGO),
E(PROC_TGID_EXE, "exe", S_IFLNK|S_IRWXUGO),
E(PROC_TGID_MOUNTS, "mounts", S_IFREG|S_IRUGO),
+ E(PROC_TGID_RLIMIT, "rlimit", S_IFREG|S_IRUGO),
#ifdef CONFIG_SECURITY
E(PROC_TGID_ATTR, "attr", S_IFDIR|S_IRUGO|S_IXUGO),
#endif
@@ -149,6 +153,7 @@
E(PROC_TID_ROOT, "root", S_IFLNK|S_IRWXUGO),
E(PROC_TID_EXE, "exe", S_IFLNK|S_IRWXUGO),
E(PROC_TID_MOUNTS, "mounts", S_IFREG|S_IRUGO),
+ E(PROC_TID_RLIMIT, "rlimit", S_IFREG|S_IRUGO),
#ifdef CONFIG_SECURITY
E(PROC_TID_ATTR, "attr", S_IFDIR|S_IRUGO|S_IXUGO),
#endif
@@ -496,6 +501,107 @@
.release = mounts_release,
};

+const char * const rlim_name[RLIM_NLIMITS] = {
+#ifdef RLIMIT_CPU
+ [RLIMIT_CPU] = "cpu",
+#endif
+#ifdef RLIMIT_FSIZE
+ [RLIMIT_FSIZE] = "fsize",
+#endif
+#ifdef RLIMIT_DATA
+ [RLIMIT_DATA] = "data",
+#endif
+#ifdef RLIMIT_STACK
+ [RLIMIT_STACK] = "stack",
+#endif
+#ifdef RLIMIT_CORE
+ [RLIMIT_CORE] = "core",
+#endif
+#ifdef RLIMIT_RSS
+ [RLIMIT_RSS] = "rss",
+#endif
+#ifdef RLIMIT_NPROC
+ [RLIMIT_NPROC] = "nproc",
+#endif
+#ifdef RLIMIT_NOFILE
+ [RLIMIT_NOFILE] = "nofile",
+#endif
+#ifdef RLIMIT_MEMLOCK
+ [RLIMIT_MEMLOCK] = "memlock",
+#endif
+#ifdef RLIMIT_AS
+ [RLIMIT_AS] = "as",
+#endif
+#ifdef RLIMIT_LOCKS
+ [RLIMIT_LOCKS] = "locks",
+#endif
+#ifdef RLIMIT_SIGPENDING
+ [RLIMIT_SIGPENDING] = "sigpending",
+#endif
+#ifdef RLIMIT_MSGQUEUE
+ [RLIMIT_MSGQUEUE] = "msgqueue",
+#endif
+};
+
+static int rlimit_show(struct seq_file *s, void *v)
+{
+ struct rlimit *rlim = (struct rlimit *) s->private;
+ int i;
+
+ for (i = 0 ; i < RLIM_NLIMITS ; i++) {
+ if (rlim_name[i] != NULL)
+ seq_puts(s, rlim_name[i]);
+ else
+ seq_printf(s, "rlimit-%d", i);
+
+ if (rlim[i].rlim_cur == RLIM_INFINITY)
+ seq_puts(s, " unlimited");
+ else
+ seq_printf(s, " %lu", (unsigned long)rlim[i].rlim_cur);
+
+ if (rlim[i].rlim_max == RLIM_INFINITY)
+ seq_puts(s, " unlimited\n");
+ else
+ seq_printf(s, " %lu\n", (unsigned long)rlim[i].rlim_max);
+ }
+ return 0;
+}
+
+static int rlimit_open(struct inode *inode, struct file *file)
+{
+ struct task_struct *task = proc_task(inode);
+ struct rlimit *rlim = kmalloc(RLIM_NLIMITS * sizeof (struct rlimit), GFP_KERNEL);
+ int ret;
+
+ if (!rlim)
+ return -ENOMEM;
+
+ task_lock(task->group_leader);
+ memcpy(rlim, task->signal->rlim, RLIM_NLIMITS * sizeof (struct rlimit));
+ task_unlock(task->group_leader);
+
+ ret = single_open(file, rlimit_show, rlim);
+
+ if (ret)
+ kfree(rlim);
+
+ return ret;
+}
+
+static int rlimit_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *s = file->private_data;
+ kfree(s->private);
+ return single_release(inode, file);
+}
+
+static struct file_operations proc_rlimit_operations = {
+ .open = rlimit_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = rlimit_release,
+};
+
#define PROC_BLOCK_SIZE (3*1024) /* 4K page size but our output routines use some slack for overruns */

static ssize_t proc_info_read(struct file * file, char __user * buf,
@@ -1300,6 +1406,10 @@
case PROC_TGID_MOUNTS:
inode->i_fop = &proc_mounts_operations;
break;
+ case PROC_TID_RLIMIT:
+ case PROC_TGID_RLIMIT:
+ inode->i_fop = &proc_rlimit_operations;
+ break;
#ifdef CONFIG_SECURITY
case PROC_TID_ATTR:
inode->i_nlink = 2;


2005-01-19 07:11:51

by Chris Wright

[permalink] [raw]
Subject: Re: [RFC][PATCH] /proc/<pid>/rlimit

* Bill Rugolsky Jr. ([email protected]) wrote:
> This patch against 2.6.11-rc1-bk6 adds /proc/<pid>/rlimit to export
> per-process resource limit settings. It was written to help analyze
> daemon core dump size settings, but may be more generally useful.
> Tested on 2.6.10. Sample output:

I can certainly see how it could be useful for debugging. Perhaps it
should be available to only oneself (like getrlimit restriction) or
CAP_SYS_RESOURCE processes? (Though, I'm not sure how useful the data
would be to a malicious user). Also, since the format is both arch
dependent and release dependent I guess it's not ideal for anything that
depends on the format.

> +const char * const rlim_name[RLIM_NLIMITS] = {
> +#ifdef RLIMIT_CPU
> + [RLIMIT_CPU] = "cpu",
> +#endif

BTW, when I went through the resource.h files, I didn't notice any that
leftout rlimits, it was only about ordering. So I don't think those
ifdefs are necessary.

thanks,
-chris
--
Linux Security Modules http://lsm.immunix.org http://lsm.bkbits.net

2005-01-19 19:32:13

by Jan Knutar

[permalink] [raw]
Subject: Re: [RFC][PATCH] /proc/<pid>/rlimit

On Tuesday 18 January 2005 22:44, Bill Rugolsky Jr. wrote:
> This patch against 2.6.11-rc1-bk6 adds /proc/<pid>/rlimit to export
> per-process resource limit settings. It was written to help analyze
> daemon core dump size settings, but may be more generally useful.
> Tested on 2.6.10. Sample output:

A "cool feature" would be if you could do
echo nofile 8192 8192 >/proc/`pidof thatserverproess`/rlimit
:-)

2005-01-19 19:38:18

by Chris Wright

[permalink] [raw]
Subject: Re: [RFC][PATCH] /proc/<pid>/rlimit

* Jan Knutar ([email protected]) wrote:
> On Tuesday 18 January 2005 22:44, Bill Rugolsky Jr. wrote:
> > This patch against 2.6.11-rc1-bk6 adds /proc/<pid>/rlimit to export
> > per-process resource limit settings. It was written to help analyze
> > daemon core dump size settings, but may be more generally useful.
> > Tested on 2.6.10. Sample output:
>
> A "cool feature" would be if you could do
> echo nofile 8192 8192 >/proc/`pidof thatserverproess`/rlimit
> :-)

This is security sensitive, and is currently only expected to be changed
by current.

thanks,
-chris
--
Linux Security Modules http://lsm.immunix.org http://lsm.bkbits.net

2005-01-19 20:13:44

by Bill Rugolsky Jr.

[permalink] [raw]
Subject: Re: [RFC][PATCH] /proc/<pid>/rlimit

On Wed, Jan 19, 2005 at 11:38:03AM -0800, Chris Wright wrote:
> * Jan Knutar ([email protected]) wrote:
> > A "cool feature" would be if you could do
> > echo nofile 8192 8192 >/proc/`pidof thatserverproess`/rlimit
> > :-)
>
> This is security sensitive, and is currently only expected to be changed
> by current.

Sure, I had thought of implementing it, paused to consider the security
implications, and then punted.

Chris, on the other point that you made regarding UGO read access to "rlimit",
the same is true of "maps" (at least sans SELinux policy), so I don't
see an issue. Certainly the map information is more security sensitive.

Regards,

-Bill

2005-01-19 20:48:53

by Chris Wright

[permalink] [raw]
Subject: Re: [RFC][PATCH] /proc/<pid>/rlimit

* Bill Rugolsky Jr. ([email protected]) wrote:
> Chris, on the other point that you made regarding UGO read access to "rlimit",
> the same is true of "maps" (at least sans SELinux policy), so I don't
> see an issue. Certainly the map information is more security sensitive.

Yeah, I can't think of any offhand, just defaulting to paranoid ;-)

thanks,
-chris
--
Linux Security Modules http://lsm.immunix.org http://lsm.bkbits.net

2005-01-20 20:04:46

by Pavel Machek

[permalink] [raw]
Subject: Re: [RFC][PATCH] /proc/<pid>/rlimit

Hi!

> This patch against 2.6.11-rc1-bk6 adds /proc/<pid>/rlimit to export
> per-process resource limit settings. It was written to help analyze
> daemon core dump size settings, but may be more generally useful.
> Tested on 2.6.10. Sample output:
>
> root@tiny ~ # cat /proc/__/rlimit
> cpu unlimited unlimited
> fsize unlimited unlimited
> data unlimited unlimited
> stack 8388608 unlimited
> core 0 unlimited
> rss unlimited unlimited
> nproc 111 111
> nofile 1024 1024
> memlock 32768 32768
> as unlimited unlimited
> locks unlimited unlimited
> sigpending 1024 1024
> msgqueue 819200 819200
>
> Feedback welcome.

It would be nice if you could make it "value-per-file". That way,
it could become writable in future. If "max nice level" ever becomes rlimit,
this would be very usefull.

Pavel
--
64 bytes from 195.113.31.123: icmp_seq=28 ttl=51 time=448769.1 ms

2005-01-21 02:20:07

by Bill Rugolsky Jr.

[permalink] [raw]
Subject: Re: [RFC][PATCH] /proc/<pid>/rlimit

On Thu, Jan 20, 2005 at 03:43:58PM +0100, Pavel Machek wrote:
> It would be nice if you could make it "value-per-file". That way,
> it could become writable in future. If "max nice level" ever becomes rlimit,
> this would be very usefull.

Agreed, though write support present difficulties.

My principal concern is that we don't want users changing resource limits
of privileged processes. If we want an ordinary user to be allowed to
change limits, the rules would have to be similar to those allowed for
ptrace(), e.g., no-setuid processes, etc. [With ptrace(), one can of
course attach to the process and invoke the setrlimit() syscall directly].
Additionally, sys_setrlimit() has an LSM hook:

security_task_setrlimit(unsigned int resource, struct rlimit *)

One would need to take account of changing the limit from a different
context. It's a bit of a mess, and outside of the standard API; that's
why I didn't bother.

Anyway, for Jan, here's my incomplete and unmergeable cut-n-paste hack
to implement write on top of my previous patch. Format is as was
suggested by Jan:

<name|[rlimit-]%u> <%u|unlimited> <%u|unlimited>

E.g.,
echo memlock 65536 65536 > /proc/1/rlimit

Writing is limited to root (i.e. CAP_SYS_PTRACE), though see
fs/proc/base.c:may_ptrace_attach() for an idea of how to change that.

-Bill


--- linux-2.6.11-rc1-bk6/fs/proc/base.c.proc-pid-rlimit-write
+++ linux-2.6.11-rc1-bk6/fs/proc/base.c
@@ -23,6 +23,7 @@
#include <linux/init.h>
#include <linux/file.h>
#include <linux/string.h>
+#include <linux/ctype.h>
#include <linux/seq_file.h>
#include <linux/namei.h>
#include <linux/namespace.h>
@@ -127,7 +128,7 @@
E(PROC_TGID_ROOT, "root", S_IFLNK|S_IRWXUGO),
E(PROC_TGID_EXE, "exe", S_IFLNK|S_IRWXUGO),
E(PROC_TGID_MOUNTS, "mounts", S_IFREG|S_IRUGO),
- E(PROC_TGID_RLIMIT, "rlimit", S_IFREG|S_IRUGO),
+ E(PROC_TGID_RLIMIT, "rlimit", S_IFREG|S_IRUGO|S_IWUSR),
#ifdef CONFIG_SECURITY
E(PROC_TGID_ATTR, "attr", S_IFDIR|S_IRUGO|S_IXUGO),
#endif
@@ -153,7 +154,7 @@
E(PROC_TID_ROOT, "root", S_IFLNK|S_IRWXUGO),
E(PROC_TID_EXE, "exe", S_IFLNK|S_IRWXUGO),
E(PROC_TID_MOUNTS, "mounts", S_IFREG|S_IRUGO),
- E(PROC_TID_RLIMIT, "rlimit", S_IFREG|S_IRUGO),
+ E(PROC_TID_RLIMIT, "rlimit", S_IFREG|S_IRUGO|S_IWUSR),
#ifdef CONFIG_SECURITY
E(PROC_TID_ATTR, "attr", S_IFDIR|S_IRUGO|S_IXUGO),
#endif
@@ -595,9 +596,99 @@
return single_release(inode, file);
}

+static inline char *skip_ws(char *s)
+{
+ while (isspace(*s))
+ s++;
+ return s;
+}
+
+static inline char *find_ws(char *s)
+{
+ while (!isspace(*s) && *s != '\0')
+ s++;
+ return s;
+}
+
+#define MAX_RLIMIT_WRITE 79
+static ssize_t rlimit_write(struct file * file, const char * buf,
+ size_t count, loff_t *ppos)
+{
+ struct task_struct *task = proc_task(file->f_dentry->d_inode);
+ struct rlimit new_rlim, *old_rlim;
+ unsigned int i;
+ char *s, *t, kbuf[MAX_RLIMIT_WRITE+1];
+
+ /* changing resources limits can crash or subvert a process */
+ if (!capable(CAP_SYS_PTRACE) || security_ptrace(current,task))
+ return -ESRCH;
+
+ if (count > MAX_RLIMIT_WRITE)
+ return -EINVAL;
+ if (copy_from_user(&kbuf, buf, count))
+ return -EFAULT;
+ kbuf[MAX_RLIMIT_WRITE] = '\0';
+
+ /* parse the resource id */
+ s = skip_ws(kbuf);
+ t = find_ws(s);
+ if (*t == '\0')
+ return -EINVAL;
+ *t++ = '\0';
+ for (i = 0 ; i < RLIM_NLIMITS ; i++)
+ if (rlim_name[i] && !strcmp(s,rlim_name[i]))
+ break;
+ if (i >= RLIM_NLIMITS) {
+ if (!strncmp(s, "rlimit-",7))
+ s += 7;
+ if (sscanf(s, "%u", &i) != 1 || i >= RLIM_NLIMITS)
+ return -EINVAL;
+ }
+
+ /* parse the soft limit */
+ s = skip_ws(t);
+ t = find_ws(s);
+ if (*t == '\0')
+ return -EINVAL;
+ *t++ = '\0';
+ if (!strcmp(s, "unlimited"))
+ new_rlim.rlim_cur = RLIM_INFINITY;
+ else if (sscanf(s, "%lu", &new_rlim.rlim_cur) != 1)
+ return -EINVAL;
+
+ /* parse the hard limit */
+ s = skip_ws(t);
+ t = find_ws(s);
+ *t = '\0';
+ if (!strcmp(s, "unlimited"))
+ new_rlim.rlim_max = RLIM_INFINITY;
+ else if (sscanf(s, "%lu", &new_rlim.rlim_max) != 1)
+ return -EINVAL;
+
+ /* validate the values; copied from sys_setrlimit() */
+ if (new_rlim.rlim_cur > new_rlim.rlim_max)
+ return -EINVAL;
+ old_rlim = task->signal->rlim + i;
+ if ((new_rlim.rlim_max > old_rlim->rlim_max) &&
+ !capable(CAP_SYS_RESOURCE))
+ return -EPERM;
+ if (i == RLIMIT_NOFILE && new_rlim.rlim_max > NR_OPEN)
+ return -EPERM;
+
+ /* SECURITY: missing security_task_setrlimit() equivalent */
+
+ /* set the limits */
+ task_lock(task->group_leader);
+ *old_rlim = new_rlim;
+ task_unlock(task->group_leader);
+
+ return count;
+}
+
static struct file_operations proc_rlimit_operations = {
.open = rlimit_open,
.read = seq_read,
+ .write = rlimit_write,
.llseek = seq_lseek,
.release = rlimit_release,
};