summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--fs/proc/proc_sysctl.c4
-rw-r--r--include/linux/sysctl.h7
-rw-r--r--kernel/sysctl.c25
3 files changed, 26 insertions, 10 deletions
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 5e31585292a0..5acc001d49f6 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -190,7 +190,7 @@ static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf,
* and won't be until we finish.
*/
error = -EPERM;
- if (sysctl_perm(table, write ? MAY_WRITE : MAY_READ))
+ if (sysctl_perm(head->root, table, write ? MAY_WRITE : MAY_READ))
goto out;
/* careful: calling conventions are nasty here */
@@ -388,7 +388,7 @@ static int proc_sys_permission(struct inode *inode, int mask, struct nameidata *
goto out;
/* Use the permissions on the sysctl table entry */
- error = sysctl_perm(table, mask);
+ error = sysctl_perm(head->root, table, mask);
out:
sysctl_head_finish(head);
return error;
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 39eafd8f97a3..24141b4d1a11 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -945,11 +945,14 @@ enum
/* For the /proc/sys support */
struct ctl_table;
struct nsproxy;
+struct ctl_table_root;
+
extern struct ctl_table_header *sysctl_head_next(struct ctl_table_header *prev);
extern struct ctl_table_header *__sysctl_head_next(struct nsproxy *namespaces,
struct ctl_table_header *prev);
extern void sysctl_head_finish(struct ctl_table_header *prev);
-extern int sysctl_perm(struct ctl_table *table, int op);
+extern int sysctl_perm(struct ctl_table_root *root,
+ struct ctl_table *table, int op);
typedef struct ctl_table ctl_table;
@@ -1049,6 +1052,8 @@ struct ctl_table_root {
struct list_head header_list;
struct list_head *(*lookup)(struct ctl_table_root *root,
struct nsproxy *namespaces);
+ int (*permissions)(struct ctl_table_root *root,
+ struct nsproxy *namespaces, struct ctl_table *table);
};
/* struct ctl_table_header is used to maintain dynamic lists of
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 874e813e40c8..d7ffdc59816a 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -1434,7 +1434,8 @@ void register_sysctl_root(struct ctl_table_root *root)
#ifdef CONFIG_SYSCTL_SYSCALL
/* Perform the actual read/write of a sysctl table entry. */
-static int do_sysctl_strategy(struct ctl_table *table,
+static int do_sysctl_strategy(struct ctl_table_root *root,
+ struct ctl_table *table,
int __user *name, int nlen,
void __user *oldval, size_t __user *oldlenp,
void __user *newval, size_t newlen)
@@ -1445,7 +1446,7 @@ static int do_sysctl_strategy(struct ctl_table *table,
op |= 004;
if (newval)
op |= 002;
- if (sysctl_perm(table, op))
+ if (sysctl_perm(root, table, op))
return -EPERM;
if (table->strategy) {
@@ -1471,6 +1472,7 @@ static int do_sysctl_strategy(struct ctl_table *table,
static int parse_table(int __user *name, int nlen,
void __user *oldval, size_t __user *oldlenp,
void __user *newval, size_t newlen,
+ struct ctl_table_root *root,
struct ctl_table *table)
{
int n;
@@ -1485,14 +1487,14 @@ repeat:
if (n == table->ctl_name) {
int error;
if (table->child) {
- if (sysctl_perm(table, 001))
+ if (sysctl_perm(root, table, 001))
return -EPERM;
name++;
nlen--;
table = table->child;
goto repeat;
}
- error = do_sysctl_strategy(table, name, nlen,
+ error = do_sysctl_strategy(root, table, name, nlen,
oldval, oldlenp,
newval, newlen);
return error;
@@ -1518,7 +1520,8 @@ int do_sysctl(int __user *name, int nlen, void __user *oldval, size_t __user *ol
for (head = sysctl_head_next(NULL); head;
head = sysctl_head_next(head)) {
error = parse_table(name, nlen, oldval, oldlenp,
- newval, newlen, head->ctl_table);
+ newval, newlen,
+ head->root, head->ctl_table);
if (error != -ENOTDIR) {
sysctl_head_finish(head);
break;
@@ -1564,13 +1567,21 @@ static int test_perm(int mode, int op)
return -EACCES;
}
-int sysctl_perm(struct ctl_table *table, int op)
+int sysctl_perm(struct ctl_table_root *root, struct ctl_table *table, int op)
{
int error;
+ int mode;
+
error = security_sysctl(table, op);
if (error)
return error;
- return test_perm(table->mode, op);
+
+ if (root->permissions)
+ mode = root->permissions(root, current->nsproxy, table);
+ else
+ mode = table->mode;
+
+ return test_perm(mode, op);
}
static void sysctl_set_parent(struct ctl_table *parent, struct ctl_table *table)