linux/ipc/ipc_sysctl.c
Alexey Gladkov 1f5c135ee5 ipc: Store ipc sysctls in the ipc namespace
The ipc sysctls are not available for modification inside the user
namespace. Following the mqueue sysctls, we changed the implementation
to be more userns friendly.

So far, the changes do not provide additional access to files. This
will be done in a future patch.

Signed-off-by: Alexey Gladkov <legion@kernel.org>
Link: https://lkml.kernel.org/r/be6f9d014276f4dddd0c3aa05a86052856c1c555.1644862280.git.legion@kernel.org
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
2022-03-08 13:39:40 -06:00

313 lines
7.5 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2007
*
* Author: Eric Biederman <ebiederm@xmision.com>
*/
#include <linux/module.h>
#include <linux/ipc.h>
#include <linux/nsproxy.h>
#include <linux/sysctl.h>
#include <linux/uaccess.h>
#include <linux/capability.h>
#include <linux/ipc_namespace.h>
#include <linux/msg.h>
#include <linux/slab.h>
#include "util.h"
static int proc_ipc_dointvec_minmax_orphans(struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
struct ipc_namespace *ns = table->extra1;
struct ctl_table ipc_table;
int err;
memcpy(&ipc_table, table, sizeof(ipc_table));
ipc_table.extra1 = SYSCTL_ZERO;
ipc_table.extra2 = SYSCTL_ONE;
err = proc_dointvec_minmax(&ipc_table, write, buffer, lenp, ppos);
if (err < 0)
return err;
if (ns->shm_rmid_forced)
shm_destroy_orphaned(ns);
return err;
}
static int proc_ipc_auto_msgmni(struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
struct ctl_table ipc_table;
int dummy = 0;
memcpy(&ipc_table, table, sizeof(ipc_table));
ipc_table.data = &dummy;
if (write)
pr_info_once("writing to auto_msgmni has no effect");
return proc_dointvec_minmax(&ipc_table, write, buffer, lenp, ppos);
}
static int proc_ipc_sem_dointvec(struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
struct ipc_namespace *ns = table->extra1;
struct ctl_table ipc_table;
int ret, semmni;
memcpy(&ipc_table, table, sizeof(ipc_table));
ipc_table.extra1 = NULL;
ipc_table.extra2 = NULL;
semmni = ns->sem_ctls[3];
ret = proc_dointvec(table, write, buffer, lenp, ppos);
if (!ret)
ret = sem_check_semmni(current->nsproxy->ipc_ns);
/*
* Reset the semmni value if an error happens.
*/
if (ret)
ns->sem_ctls[3] = semmni;
return ret;
}
#ifdef CONFIG_CHECKPOINT_RESTORE
static int proc_ipc_dointvec_minmax_checkpoint_restore(struct ctl_table *table,
int write, void *buffer, size_t *lenp, loff_t *ppos)
{
struct ipc_namespace *ns = table->extra1;
struct ctl_table ipc_table;
if (write && !checkpoint_restore_ns_capable(ns->user_ns))
return -EPERM;
memcpy(&ipc_table, table, sizeof(ipc_table));
ipc_table.extra1 = SYSCTL_ZERO;
ipc_table.extra2 = SYSCTL_INT_MAX;
return proc_dointvec_minmax(&ipc_table, write, buffer, lenp, ppos);
}
#endif
int ipc_mni = IPCMNI;
int ipc_mni_shift = IPCMNI_SHIFT;
int ipc_min_cycle = RADIX_TREE_MAP_SIZE;
static struct ctl_table ipc_sysctls[] = {
{
.procname = "shmmax",
.data = &init_ipc_ns.shm_ctlmax,
.maxlen = sizeof(init_ipc_ns.shm_ctlmax),
.mode = 0644,
.proc_handler = proc_doulongvec_minmax,
},
{
.procname = "shmall",
.data = &init_ipc_ns.shm_ctlall,
.maxlen = sizeof(init_ipc_ns.shm_ctlall),
.mode = 0644,
.proc_handler = proc_doulongvec_minmax,
},
{
.procname = "shmmni",
.data = &init_ipc_ns.shm_ctlmni,
.maxlen = sizeof(init_ipc_ns.shm_ctlmni),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = &ipc_mni,
},
{
.procname = "shm_rmid_forced",
.data = &init_ipc_ns.shm_rmid_forced,
.maxlen = sizeof(init_ipc_ns.shm_rmid_forced),
.mode = 0644,
.proc_handler = proc_ipc_dointvec_minmax_orphans,
},
{
.procname = "msgmax",
.data = &init_ipc_ns.msg_ctlmax,
.maxlen = sizeof(init_ipc_ns.msg_ctlmax),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_INT_MAX,
},
{
.procname = "msgmni",
.data = &init_ipc_ns.msg_ctlmni,
.maxlen = sizeof(init_ipc_ns.msg_ctlmni),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = &ipc_mni,
},
{
.procname = "auto_msgmni",
.data = NULL,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_ipc_auto_msgmni,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
{
.procname = "msgmnb",
.data = &init_ipc_ns.msg_ctlmnb,
.maxlen = sizeof(init_ipc_ns.msg_ctlmnb),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_INT_MAX,
},
{
.procname = "sem",
.data = &init_ipc_ns.sem_ctls,
.maxlen = 4*sizeof(int),
.mode = 0644,
.proc_handler = proc_ipc_sem_dointvec,
},
#ifdef CONFIG_CHECKPOINT_RESTORE
{
.procname = "sem_next_id",
.data = &init_ipc_ns.ids[IPC_SEM_IDS].next_id,
.maxlen = sizeof(init_ipc_ns.ids[IPC_SEM_IDS].next_id),
.mode = 0666,
.proc_handler = proc_ipc_dointvec_minmax_checkpoint_restore,
},
{
.procname = "msg_next_id",
.data = &init_ipc_ns.ids[IPC_MSG_IDS].next_id,
.maxlen = sizeof(init_ipc_ns.ids[IPC_MSG_IDS].next_id),
.mode = 0666,
.proc_handler = proc_ipc_dointvec_minmax_checkpoint_restore,
},
{
.procname = "shm_next_id",
.data = &init_ipc_ns.ids[IPC_SHM_IDS].next_id,
.maxlen = sizeof(init_ipc_ns.ids[IPC_SHM_IDS].next_id),
.mode = 0666,
.proc_handler = proc_ipc_dointvec_minmax_checkpoint_restore,
},
#endif
{}
};
static struct ctl_table_set *set_lookup(struct ctl_table_root *root)
{
return &current->nsproxy->ipc_ns->ipc_set;
}
static int set_is_seen(struct ctl_table_set *set)
{
return &current->nsproxy->ipc_ns->ipc_set == set;
}
static struct ctl_table_root set_root = {
.lookup = set_lookup,
};
bool setup_ipc_sysctls(struct ipc_namespace *ns)
{
struct ctl_table *tbl;
setup_sysctl_set(&ns->ipc_set, &set_root, set_is_seen);
tbl = kmemdup(ipc_sysctls, sizeof(ipc_sysctls), GFP_KERNEL);
if (tbl) {
int i;
for (i = 0; i < ARRAY_SIZE(ipc_sysctls); i++) {
if (tbl[i].data == &init_ipc_ns.shm_ctlmax) {
tbl[i].data = &ns->shm_ctlmax;
} else if (tbl[i].data == &init_ipc_ns.shm_ctlall) {
tbl[i].data = &ns->shm_ctlall;
} else if (tbl[i].data == &init_ipc_ns.shm_ctlmni) {
tbl[i].data = &ns->shm_ctlmni;
} else if (tbl[i].data == &init_ipc_ns.shm_rmid_forced) {
tbl[i].data = &ns->shm_rmid_forced;
tbl[i].extra1 = ns;
} else if (tbl[i].data == &init_ipc_ns.msg_ctlmax) {
tbl[i].data = &ns->msg_ctlmax;
} else if (tbl[i].data == &init_ipc_ns.msg_ctlmni) {
tbl[i].data = &ns->msg_ctlmni;
} else if (tbl[i].data == &init_ipc_ns.msg_ctlmnb) {
tbl[i].data = &ns->msg_ctlmnb;
} else if (tbl[i].data == &init_ipc_ns.sem_ctls) {
tbl[i].data = &ns->sem_ctls;
tbl[i].extra1 = ns;
#ifdef CONFIG_CHECKPOINT_RESTORE
} else if (tbl[i].data == &init_ipc_ns.ids[IPC_SEM_IDS].next_id) {
tbl[i].data = &ns->ids[IPC_SEM_IDS].next_id;
tbl[i].extra1 = ns;
} else if (tbl[i].data == &init_ipc_ns.ids[IPC_MSG_IDS].next_id) {
tbl[i].data = &ns->ids[IPC_MSG_IDS].next_id;
tbl[i].extra1 = ns;
} else if (tbl[i].data == &init_ipc_ns.ids[IPC_SHM_IDS].next_id) {
tbl[i].data = &ns->ids[IPC_SHM_IDS].next_id;
tbl[i].extra1 = ns;
#endif
} else {
tbl[i].data = NULL;
}
}
ns->ipc_sysctls = __register_sysctl_table(&ns->ipc_set, "kernel", tbl);
}
if (!ns->ipc_sysctls) {
kfree(tbl);
retire_sysctl_set(&ns->ipc_set);
return false;
}
return true;
}
void retire_ipc_sysctls(struct ipc_namespace *ns)
{
struct ctl_table *tbl;
tbl = ns->ipc_sysctls->ctl_table_arg;
unregister_sysctl_table(ns->ipc_sysctls);
retire_sysctl_set(&ns->ipc_set);
kfree(tbl);
}
static int __init ipc_sysctl_init(void)
{
if (!setup_ipc_sysctls(&init_ipc_ns)) {
pr_warn("ipc sysctl registration failed\n");
return -ENOMEM;
}
return 0;
}
device_initcall(ipc_sysctl_init);
static int __init ipc_mni_extend(char *str)
{
ipc_mni = IPCMNI_EXTEND;
ipc_mni_shift = IPCMNI_EXTEND_SHIFT;
ipc_min_cycle = IPCMNI_EXTEND_MIN_CYCLE;
pr_info("IPCMNI extended to %d.\n", ipc_mni);
return 0;
}
early_param("ipcmni_extend", ipc_mni_extend);