diff options
| author | Niklas Cassel <cassel@kernel.org> | 2026-09-18 16:06:42 +0200 |
|---|---|---|
| committer | Jens Axboe <axboe@kernel.dk> | 2026-09-29 12:54:47 -0600 |
| commit | 684b413b5483f57c890c171b9400076a0143b918 (patch) | |
| tree | f167daddad3dce9af44773541373eafbccc9ac61 /ipc | |
| download | linux-stable-684b413b5483f57c890c171b9400076a0143b918.tar.gz linux-stable-684b413b5483f57c890c171b9400076a0143b918.zip | |
virtio_blk: set the zone write granularitygrafted
virtblk_read_zoned_limits() reads the write granularity that the device
reports in virtio_blk_zoned_characteristics and assigns it to the
physical block size and to io_min, but never to the limit that is named
after it. queue_limits.zone_write_granularity is left at zero, so
blk_validate_zoned_limits() raises it to the logical block size:
if (lim->zone_write_granularity < lim->logical_block_size)
lim->zone_write_granularity = lim->logical_block_size;
A device that reports a granularity coarser than its logical block size,
which is what the field exists to express, therefore has it silently
reduced. A 512e host managed disk passed through to a guest reports a
logical block size of 512 and a write granularity of 4096, and the guest
ends up with a zone write granularity of 512.
bio_split_alignment() returns lim->zone_write_granularity if it is non-zero
and bio_split_io_at() may split a bio with as per bio_split_alignment().
This can real to the write getting rejected by the host drive, as the write
is not aligned to the physical block size.
zonefs also takes its block size from bdev_zone_write_granularity(), so it
would incorrectly use 512 on a disk that requires 4096.
sd_zbc_read_zones() sets the limit from the physical block size for the
same reason. NVMe ZNS and null_blk leave it unset, but the fallback
gives the right answer for them, as their write granularity is the
logical block size. virtio carries a separate value that may exceed it.
Set the zone write granularity from the value that the device reports.
Fixes: 95bfec41bd3d ("virtio-blk: add support for zoned block devices")
Signed-off-by: Niklas Cassel <cassel@kernel.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Link: https://patch.msgid.link/20260918140641.2031075-2-cassel@kernel.org
Signed-off-by: Jens Axboe <axboe@kernel.dk>
Diffstat (limited to 'ipc')
| -rw-r--r-- | ipc/Makefile | 12 | ||||
| -rw-r--r-- | ipc/compat.c | 82 | ||||
| -rw-r--r-- | ipc/ipc_sysctl.c | 334 | ||||
| -rw-r--r-- | ipc/mq_sysctl.c | 167 | ||||
| -rw-r--r-- | ipc/mqueue.c | 1679 | ||||
| -rw-r--r-- | ipc/msg.c | 1376 | ||||
| -rw-r--r-- | ipc/msgutil.c | 192 | ||||
| -rw-r--r-- | ipc/namespace.c | 257 | ||||
| -rw-r--r-- | ipc/sem.c | 2484 | ||||
| -rw-r--r-- | ipc/shm.c | 1883 | ||||
| -rw-r--r-- | ipc/syscall.c | 211 | ||||
| -rw-r--r-- | ipc/util.c | 928 | ||||
| -rw-r--r-- | ipc/util.h | 292 |
13 files changed, 9897 insertions, 0 deletions
diff --git a/ipc/Makefile b/ipc/Makefile new file mode 100644 index 000000000..c2558c430 --- /dev/null +++ b/ipc/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the linux ipc. +# + +obj-$(CONFIG_SYSVIPC_COMPAT) += compat.o +obj-$(CONFIG_SYSVIPC) += util.o msgutil.o msg.o sem.o shm.o syscall.o +obj-$(CONFIG_SYSVIPC_SYSCTL) += ipc_sysctl.o +obj-$(CONFIG_POSIX_MQUEUE) += mqueue.o msgutil.o +obj-$(CONFIG_IPC_NS) += namespace.o +obj-$(CONFIG_POSIX_MQUEUE_SYSCTL) += mq_sysctl.o + diff --git a/ipc/compat.c b/ipc/compat.c new file mode 100644 index 000000000..5ab822592 --- /dev/null +++ b/ipc/compat.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 32 bit compatibility code for System V IPC + * + * Copyright (C) 1997,1998 Jakub Jelinek (jj@sunsite.mff.cuni.cz) + * Copyright (C) 1997 David S. Miller (davem@caip.rutgers.edu) + * Copyright (C) 1999 Arun Sharma <arun.sharma@intel.com> + * Copyright (C) 2000 VA Linux Co + * Copyright (C) 2000 Don Dugger <n0ano@valinux.com> + * Copyright (C) 2000 Hewlett-Packard Co. + * Copyright (C) 2000 David Mosberger-Tang <davidm@hpl.hp.com> + * Copyright (C) 2000 Gerhard Tonn (ton@de.ibm.com) + * Copyright (C) 2000-2002 Andi Kleen, SuSE Labs (x86-64 port) + * Copyright (C) 2000 Silicon Graphics, Inc. + * Copyright (C) 2001 IBM + * Copyright (C) 2004 IBM Deutschland Entwicklung GmbH, IBM Corporation + * Copyright (C) 2004 Arnd Bergmann (arnd@arndb.de) + * + * This code is collected from the versions for sparc64, mips64, s390x, ia64, + * ppc64 and x86_64, all of which are based on the original sparc64 version + * by Jakub Jelinek. + * + */ +#include <linux/compat.h> +#include <linux/errno.h> +#include <linux/highuid.h> +#include <linux/init.h> +#include <linux/msg.h> +#include <linux/shm.h> +#include <linux/syscalls.h> +#include <linux/ptrace.h> + +#include <linux/mutex.h> +#include <linux/uaccess.h> + +#include "util.h" + +int get_compat_ipc64_perm(struct ipc64_perm *to, + struct compat_ipc64_perm __user *from) +{ + struct compat_ipc64_perm v; + if (copy_from_user(&v, from, sizeof(v))) + return -EFAULT; + to->uid = v.uid; + to->gid = v.gid; + to->mode = v.mode; + return 0; +} + +int get_compat_ipc_perm(struct ipc64_perm *to, + struct compat_ipc_perm __user *from) +{ + struct compat_ipc_perm v; + if (copy_from_user(&v, from, sizeof(v))) + return -EFAULT; + to->uid = v.uid; + to->gid = v.gid; + to->mode = v.mode; + return 0; +} + +void to_compat_ipc64_perm(struct compat_ipc64_perm *to, struct ipc64_perm *from) +{ + to->key = from->key; + to->uid = from->uid; + to->gid = from->gid; + to->cuid = from->cuid; + to->cgid = from->cgid; + to->mode = from->mode; + to->seq = from->seq; +} + +void to_compat_ipc_perm(struct compat_ipc_perm *to, struct ipc64_perm *from) +{ + to->key = from->key; + SET_UID(to->uid, from->uid); + SET_GID(to->gid, from->gid); + SET_UID(to->cuid, from->cuid); + SET_GID(to->cgid, from->cgid); + to->mode = from->mode; + to->seq = from->seq; +} diff --git a/ipc/ipc_sysctl.c b/ipc/ipc_sysctl.c new file mode 100644 index 000000000..d038d9442 --- /dev/null +++ b/ipc/ipc_sysctl.c @@ -0,0 +1,334 @@ +// 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 <linux/cred.h> +#include "util.h" + +static int proc_ipc_dointvec_minmax_orphans(const struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos) +{ + struct ipc_namespace *ns = + container_of(table->data, struct ipc_namespace, shm_rmid_forced); + int err; + + err = proc_dointvec_minmax(table, write, buffer, lenp, ppos); + + if (err < 0) + return err; + if (write && ns->shm_rmid_forced) + shm_destroy_orphaned(ns); + return err; +} + +static int proc_ipc_auto_msgmni(const 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(const struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos) +{ + struct ipc_namespace *ns = + container_of(table->data, struct ipc_namespace, sem_ctls); + int ret, semmni; + + semmni = ns->sem_ctls[3]; + ret = proc_dointvec(table, write, buffer, lenp, ppos); + + if (!ret) + ret = sem_check_semmni(ns); + + /* + * Reset the semmni value if an error happens. + */ + if (ret) + ns->sem_ctls[3] = semmni; + return ret; +} + +int ipc_mni = IPCMNI; +int ipc_mni_shift = IPCMNI_SHIFT; +int ipc_min_cycle = RADIX_TREE_MAP_SIZE; + +static const 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, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_ONE, + }, + { + .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 = 0444, + .proc_handler = proc_dointvec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_INT_MAX, + }, + { + .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 = 0444, + .proc_handler = proc_dointvec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_INT_MAX, + }, + { + .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 = 0444, + .proc_handler = proc_dointvec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_INT_MAX, + }, +#endif +}; + +static struct ctl_table_set *set_lookup(struct ctl_table_root *root) +{ + return ¤t->nsproxy->ipc_ns->ipc_set; +} + +static int set_is_seen(struct ctl_table_set *set) +{ + return ¤t->nsproxy->ipc_ns->ipc_set == set; +} + +static void ipc_set_ownership(struct ctl_table_header *head, + kuid_t *uid, kgid_t *gid) +{ + struct ipc_namespace *ns = + container_of(head->set, struct ipc_namespace, ipc_set); + + kuid_t ns_root_uid = make_kuid(ns->user_ns, 0); + kgid_t ns_root_gid = make_kgid(ns->user_ns, 0); + + *uid = uid_valid(ns_root_uid) ? ns_root_uid : GLOBAL_ROOT_UID; + *gid = gid_valid(ns_root_gid) ? ns_root_gid : GLOBAL_ROOT_GID; +} + +static int ipc_permissions(struct ctl_table_header *head, const struct ctl_table *table) +{ + int mode = table->mode; + +#ifdef CONFIG_CHECKPOINT_RESTORE + struct ipc_namespace *ns = + container_of(head->set, struct ipc_namespace, ipc_set); + + if (((table->data == &ns->ids[IPC_SEM_IDS].next_id) || + (table->data == &ns->ids[IPC_MSG_IDS].next_id) || + (table->data == &ns->ids[IPC_SHM_IDS].next_id)) && + checkpoint_restore_ns_capable_noaudit(ns->user_ns)) + mode = 0666; + else +#endif + { + kuid_t ns_root_uid; + kgid_t ns_root_gid; + + ipc_set_ownership(head, &ns_root_uid, &ns_root_gid); + + if (uid_eq(current_euid(), ns_root_uid)) + mode >>= 6; + + else if (in_egroup_p(ns_root_gid)) + mode >>= 3; + } + + mode &= 7; + + return (mode << 6) | (mode << 3) | mode; +} + +static struct ctl_table_root set_root = { + .lookup = set_lookup, + .permissions = ipc_permissions, + .set_ownership = ipc_set_ownership, +}; + +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; + + 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; +#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; + + else if (tbl[i].data == &init_ipc_ns.ids[IPC_MSG_IDS].next_id) + tbl[i].data = &ns->ids[IPC_MSG_IDS].next_id; + + else if (tbl[i].data == &init_ipc_ns.ids[IPC_SHM_IDS].next_id) + tbl[i].data = &ns->ids[IPC_SHM_IDS].next_id; +#endif + else + tbl[i].data = NULL; + } + + ns->ipc_sysctls = __register_sysctl_table(&ns->ipc_set, "kernel", tbl, + ARRAY_SIZE(ipc_sysctls)); + } + if (!ns->ipc_sysctls) { + kfree(tbl); + retire_sysctl_set(&ns->ipc_set); + return false; + } + + return true; +} + +void retire_ipc_sysctls(struct ipc_namespace *ns) +{ + const 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); diff --git a/ipc/mq_sysctl.c b/ipc/mq_sysctl.c new file mode 100644 index 000000000..0dd12e1c9 --- /dev/null +++ b/ipc/mq_sysctl.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2007 IBM Corporation + * + * Author: Cedric Le Goater <clg@fr.ibm.com> + */ + +#include <linux/nsproxy.h> +#include <linux/ipc_namespace.h> +#include <linux/sysctl.h> + +#include <linux/stat.h> +#include <linux/capability.h> +#include <linux/slab.h> +#include <linux/cred.h> + +static int msg_max_limit_min = MIN_MSGMAX; +static int msg_max_limit_max = HARD_MSGMAX; + +static int msg_maxsize_limit_min = MIN_MSGSIZEMAX; +static int msg_maxsize_limit_max = HARD_MSGSIZEMAX; + +static const struct ctl_table mq_sysctls[] = { + { + .procname = "queues_max", + .data = &init_ipc_ns.mq_queues_max, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, + { + .procname = "msg_max", + .data = &init_ipc_ns.mq_msg_max, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &msg_max_limit_min, + .extra2 = &msg_max_limit_max, + }, + { + .procname = "msgsize_max", + .data = &init_ipc_ns.mq_msgsize_max, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &msg_maxsize_limit_min, + .extra2 = &msg_maxsize_limit_max, + }, + { + .procname = "msg_default", + .data = &init_ipc_ns.mq_msg_default, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &msg_max_limit_min, + .extra2 = &msg_max_limit_max, + }, + { + .procname = "msgsize_default", + .data = &init_ipc_ns.mq_msgsize_default, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &msg_maxsize_limit_min, + .extra2 = &msg_maxsize_limit_max, + }, +}; + +static struct ctl_table_set *set_lookup(struct ctl_table_root *root) +{ + return ¤t->nsproxy->ipc_ns->mq_set; +} + +static int set_is_seen(struct ctl_table_set *set) +{ + return ¤t->nsproxy->ipc_ns->mq_set == set; +} + +static void mq_set_ownership(struct ctl_table_header *head, + kuid_t *uid, kgid_t *gid) +{ + struct ipc_namespace *ns = + container_of(head->set, struct ipc_namespace, mq_set); + + kuid_t ns_root_uid = make_kuid(ns->user_ns, 0); + kgid_t ns_root_gid = make_kgid(ns->user_ns, 0); + + *uid = uid_valid(ns_root_uid) ? ns_root_uid : GLOBAL_ROOT_UID; + *gid = gid_valid(ns_root_gid) ? ns_root_gid : GLOBAL_ROOT_GID; +} + +static int mq_permissions(struct ctl_table_header *head, const struct ctl_table *table) +{ + int mode = table->mode; + kuid_t ns_root_uid; + kgid_t ns_root_gid; + + mq_set_ownership(head, &ns_root_uid, &ns_root_gid); + + if (uid_eq(current_euid(), ns_root_uid)) + mode >>= 6; + + else if (in_egroup_p(ns_root_gid)) + mode >>= 3; + + mode &= 7; + + return (mode << 6) | (mode << 3) | mode; +} + +static struct ctl_table_root set_root = { + .lookup = set_lookup, + .permissions = mq_permissions, + .set_ownership = mq_set_ownership, +}; + +bool setup_mq_sysctls(struct ipc_namespace *ns) +{ + struct ctl_table *tbl; + + setup_sysctl_set(&ns->mq_set, &set_root, set_is_seen); + + tbl = kmemdup(mq_sysctls, sizeof(mq_sysctls), GFP_KERNEL); + if (tbl) { + int i; + + for (i = 0; i < ARRAY_SIZE(mq_sysctls); i++) { + if (tbl[i].data == &init_ipc_ns.mq_queues_max) + tbl[i].data = &ns->mq_queues_max; + + else if (tbl[i].data == &init_ipc_ns.mq_msg_max) + tbl[i].data = &ns->mq_msg_max; + + else if (tbl[i].data == &init_ipc_ns.mq_msgsize_max) + tbl[i].data = &ns->mq_msgsize_max; + + else if (tbl[i].data == &init_ipc_ns.mq_msg_default) + tbl[i].data = &ns->mq_msg_default; + + else if (tbl[i].data == &init_ipc_ns.mq_msgsize_default) + tbl[i].data = &ns->mq_msgsize_default; + else + tbl[i].data = NULL; + } + + ns->mq_sysctls = __register_sysctl_table(&ns->mq_set, + "fs/mqueue", tbl, + ARRAY_SIZE(mq_sysctls)); + } + if (!ns->mq_sysctls) { + kfree(tbl); + retire_sysctl_set(&ns->mq_set); + return false; + } + + return true; +} + +void retire_mq_sysctls(struct ipc_namespace *ns) +{ + const struct ctl_table *tbl; + + tbl = ns->mq_sysctls->ctl_table_arg; + unregister_sysctl_table(ns->mq_sysctls); + retire_sysctl_set(&ns->mq_set); + kfree(tbl); +} diff --git a/ipc/mqueue.c b/ipc/mqueue.c new file mode 100644 index 000000000..d1dd36a65 --- /dev/null +++ b/ipc/mqueue.c @@ -0,0 +1,1679 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * POSIX message queues filesystem for Linux. + * + * Copyright (C) 2003,2004 Krzysztof Benedyczak (golbi@mat.uni.torun.pl) + * Michal Wronski (michal.wronski@gmail.com) + * + * Spinlocks: Mohamed Abbas (abbas.mohamed@intel.com) + * Lockless receive & send, fd based notify: + * Manfred Spraul (manfred@colorfullife.com) + * + * Audit: George Wilson (ltcgcw@us.ibm.com) + */ + +#include <linux/capability.h> +#include <linux/init.h> +#include <linux/pagemap.h> +#include <linux/file.h> +#include <linux/mount.h> +#include <linux/fs_context.h> +#include <linux/namei.h> +#include <linux/sysctl.h> +#include <linux/poll.h> +#include <linux/mqueue.h> +#include <linux/msg.h> +#include <linux/skbuff.h> +#include <linux/vmalloc.h> +#include <linux/netlink.h> +#include <linux/syscalls.h> +#include <linux/audit.h> +#include <linux/signal.h> +#include <linux/mutex.h> +#include <linux/nsproxy.h> +#include <linux/pid.h> +#include <linux/ipc_namespace.h> +#include <linux/user_namespace.h> +#include <linux/slab.h> +#include <linux/sched/wake_q.h> +#include <linux/sched/signal.h> +#include <linux/sched/user.h> + +#include <net/sock.h> +#include "util.h" + +struct mqueue_fs_context { + struct ipc_namespace *ipc_ns; + bool newns; /* Set if newly created ipc namespace */ +}; + +#define MQUEUE_MAGIC 0x19800202 +#define DIRENT_SIZE 20 +#define FILENT_SIZE 80 + +#define SEND 0 +#define RECV 1 + +#define STATE_NONE 0 +#define STATE_READY 1 + +struct posix_msg_tree_node { + struct rb_node rb_node; + struct list_head msg_list; + int priority; +}; + +/* + * Locking: + * + * Accesses to a message queue are synchronized by acquiring info->lock. + * + * There are two notable exceptions: + * - The actual wakeup of a sleeping task is performed using the wake_q + * framework. info->lock is already released when wake_up_q is called. + * - The exit codepaths after sleeping check ext_wait_queue->state without + * any locks. If it is STATE_READY, then the syscall is completed without + * acquiring info->lock. + * + * MQ_BARRIER: + * To achieve proper release/acquire memory barrier pairing, the state is set to + * STATE_READY with smp_store_release(), and it is read with READ_ONCE followed + * by smp_acquire__after_ctrl_dep(). In addition, wake_q_add_safe() is used. + * + * This prevents the following races: + * + * 1) With the simple wake_q_add(), the task could be gone already before + * the increase of the reference happens + * Thread A + * Thread B + * WRITE_ONCE(wait.state, STATE_NONE); + * schedule_hrtimeout() + * wake_q_add(A) + * if (cmpxchg()) // success + * ->state = STATE_READY (reordered) + * <timeout returns> + * if (wait.state == STATE_READY) return; + * sysret to user space + * sys_exit() + * get_task_struct() // UaF + * + * Solution: Use wake_q_add_safe() and perform the get_task_struct() before + * the smp_store_release() that does ->state = STATE_READY. + * + * 2) Without proper _release/_acquire barriers, the woken up task + * could read stale data + * + * Thread A + * Thread B + * do_mq_timedreceive + * WRITE_ONCE(wait.state, STATE_NONE); + * schedule_hrtimeout() + * state = STATE_READY; + * <timeout returns> + * if (wait.state == STATE_READY) return; + * msg_ptr = wait.msg; // Access to stale data! + * receiver->msg = message; (reordered) + * + * Solution: use _release and _acquire barriers. + * + * 3) There is intentionally no barrier when setting current->state + * to TASK_INTERRUPTIBLE: spin_unlock(&info->lock) provides the + * release memory barrier, and the wakeup is triggered when holding + * info->lock, i.e. spin_lock(&info->lock) provided a pairing + * acquire memory barrier. + */ + +struct ext_wait_queue { /* queue of sleeping tasks */ + struct task_struct *task; + struct list_head list; + struct msg_msg *msg; /* ptr of loaded message */ + int state; /* one of STATE_* values */ +}; + +struct mqueue_inode_info { + spinlock_t lock; + struct inode vfs_inode; + wait_queue_head_t wait_q; + + struct rb_root msg_tree; + struct rb_node *msg_tree_rightmost; + struct posix_msg_tree_node *node_cache; + struct mq_attr attr; + + struct sigevent notify; + struct pid *notify_owner; + u32 notify_self_exec_id; + struct user_namespace *notify_user_ns; + struct ucounts *ucounts; /* user who created, for accounting */ + struct sock *notify_sock; + struct sk_buff *notify_cookie; + + /* for tasks waiting for free space and messages, respectively */ + struct ext_wait_queue e_wait_q[2]; + + unsigned long qsize; /* size of queue in memory (sum of all msgs) */ +}; + +static struct file_system_type mqueue_fs_type; +static const struct inode_operations mqueue_dir_inode_operations; +static const struct file_operations mqueue_file_operations; +static const struct super_operations mqueue_super_ops; +static const struct fs_context_operations mqueue_fs_context_ops; +static void remove_notification(struct mqueue_inode_info *info); + +static struct kmem_cache *mqueue_inode_cachep; + +static inline struct mqueue_inode_info *MQUEUE_I(struct inode *inode) +{ + return container_of(inode, struct mqueue_inode_info, vfs_inode); +} + +/* + * This routine should be called with the mq_lock held. + */ +static inline struct ipc_namespace *__get_ns_from_inode(struct inode *inode) +{ + return get_ipc_ns(inode->i_sb->s_fs_info); +} + +static struct ipc_namespace *get_ns_from_inode(struct inode *inode) +{ + struct ipc_namespace *ns; + + spin_lock(&mq_lock); + ns = __get_ns_from_inode(inode); + spin_unlock(&mq_lock); + return ns; +} + +/* Auxiliary functions to manipulate messages' list */ +static int msg_insert(struct msg_msg *msg, struct mqueue_inode_info *info) +{ + struct rb_node **p, *parent = NULL; + struct posix_msg_tree_node *leaf; + bool rightmost = true; + + p = &info->msg_tree.rb_node; + while (*p) { + parent = *p; + leaf = rb_entry(parent, struct posix_msg_tree_node, rb_node); + + if (likely(leaf->priority == msg->m_type)) + goto insert_msg; + else if (msg->m_type < leaf->priority) { + p = &(*p)->rb_left; + rightmost = false; + } else + p = &(*p)->rb_right; + } + if (info->node_cache) { + leaf = info->node_cache; + info->node_cache = NULL; + } else { + leaf = kmalloc_obj(*leaf, GFP_ATOMIC); + if (!leaf) + return -ENOMEM; + INIT_LIST_HEAD(&leaf->msg_list); + } + leaf->priority = msg->m_type; + + if (rightmost) + info->msg_tree_rightmost = &leaf->rb_node; + + rb_link_node(&leaf->rb_node, parent, p); + rb_insert_color(&leaf->rb_node, &info->msg_tree); +insert_msg: + info->attr.mq_curmsgs++; + info->qsize += msg->m_ts; + list_add_tail(&msg->m_list, &leaf->msg_list); + return 0; +} + +static inline void msg_tree_erase(struct posix_msg_tree_node *leaf, + struct mqueue_inode_info *info) +{ + struct rb_node *node = &leaf->rb_node; + + if (info->msg_tree_rightmost == node) + info->msg_tree_rightmost = rb_prev(node); + + rb_erase(node, &info->msg_tree); + if (info->node_cache) + kfree(leaf); + else + info->node_cache = leaf; +} + +static inline struct msg_msg *msg_get(struct mqueue_inode_info *info) +{ + struct rb_node *parent = NULL; + struct posix_msg_tree_node *leaf; + struct msg_msg *msg; + +try_again: + /* + * During insert, low priorities go to the left and high to the + * right. On receive, we want the highest priorities first, so + * walk all the way to the right. + */ + parent = info->msg_tree_rightmost; + if (!parent) { + if (info->attr.mq_curmsgs) { + pr_warn_once("Inconsistency in POSIX message queue, " + "no tree element, but supposedly messages " + "should exist!\n"); + info->attr.mq_curmsgs = 0; + } + return NULL; + } + leaf = rb_entry(parent, struct posix_msg_tree_node, rb_node); + if (unlikely(list_empty(&leaf->msg_list))) { + pr_warn_once("Inconsistency in POSIX message queue, " + "empty leaf node but we haven't implemented " + "lazy leaf delete!\n"); + msg_tree_erase(leaf, info); + goto try_again; + } else { + msg = list_first_entry(&leaf->msg_list, + struct msg_msg, m_list); + list_del(&msg->m_list); + if (list_empty(&leaf->msg_list)) { + msg_tree_erase(leaf, info); + } + } + info->attr.mq_curmsgs--; + info->qsize -= msg->m_ts; + return msg; +} + +static struct inode *mqueue_get_inode(struct super_block *sb, + struct ipc_namespace *ipc_ns, umode_t mode, + struct mq_attr *attr) +{ + struct inode *inode; + int ret = -ENOMEM; + + inode = new_inode(sb); + if (!inode) + goto err; + + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + inode->i_uid = current_fsuid(); + inode->i_gid = current_fsgid(); + simple_inode_init_ts(inode); + + if (S_ISREG(mode)) { + struct mqueue_inode_info *info; + unsigned long mq_bytes, mq_treesize; + + inode->i_fop = &mqueue_file_operations; + inode->i_size = FILENT_SIZE; + /* mqueue specific info */ + info = MQUEUE_I(inode); + spin_lock_init(&info->lock); + init_waitqueue_head(&info->wait_q); + INIT_LIST_HEAD(&info->e_wait_q[0].list); + INIT_LIST_HEAD(&info->e_wait_q[1].list); + info->notify_owner = NULL; + info->notify_user_ns = NULL; + info->qsize = 0; + info->ucounts = NULL; /* set when all is ok */ + info->msg_tree = RB_ROOT; + info->msg_tree_rightmost = NULL; + info->node_cache = NULL; + memset(&info->attr, 0, sizeof(info->attr)); + info->attr.mq_maxmsg = min(ipc_ns->mq_msg_max, + ipc_ns->mq_msg_default); + info->attr.mq_msgsize = min(ipc_ns->mq_msgsize_max, + ipc_ns->mq_msgsize_default); + if (attr) { + info->attr.mq_maxmsg = attr->mq_maxmsg; + info->attr.mq_msgsize = attr->mq_msgsize; + } + /* + * We used to allocate a static array of pointers and account + * the size of that array as well as one msg_msg struct per + * possible message into the queue size. That's no longer + * accurate as the queue is now an rbtree and will grow and + * shrink depending on usage patterns. We can, however, still + * account one msg_msg struct per message, but the nodes are + * allocated depending on priority usage, and most programs + * only use one, or a handful, of priorities. However, since + * this is pinned memory, we need to assume worst case, so + * that means the min(mq_maxmsg, max_priorities) * struct + * posix_msg_tree_node. + */ + + ret = -EINVAL; + if (info->attr.mq_maxmsg <= 0 || info->attr.mq_msgsize <= 0) + goto out_inode; + if (capable(CAP_SYS_RESOURCE)) { + if (info->attr.mq_maxmsg > HARD_MSGMAX || + info->attr.mq_msgsize > HARD_MSGSIZEMAX) + goto out_inode; + } else { + if (info->attr.mq_maxmsg > ipc_ns->mq_msg_max || + info->attr.mq_msgsize > ipc_ns->mq_msgsize_max) + goto out_inode; + } + ret = -EOVERFLOW; + /* check for overflow */ + if (info->attr.mq_msgsize > ULONG_MAX/info->attr.mq_maxmsg) + goto out_inode; + mq_treesize = info->attr.mq_maxmsg * sizeof(struct msg_msg) + + min_t(unsigned int, info->attr.mq_maxmsg, MQ_PRIO_MAX) * + sizeof(struct posix_msg_tree_node); + mq_bytes = info->attr.mq_maxmsg * info->attr.mq_msgsize; + if (mq_bytes + mq_treesize < mq_bytes) + goto out_inode; + mq_bytes += mq_treesize; + info->ucounts = get_ucounts(current_ucounts()); + if (info->ucounts) { + long msgqueue; + + spin_lock(&mq_lock); + msgqueue = inc_rlimit_ucounts(info->ucounts, UCOUNT_RLIMIT_MSGQUEUE, mq_bytes); + if (msgqueue == LONG_MAX || msgqueue > rlimit(RLIMIT_MSGQUEUE)) { + dec_rlimit_ucounts(info->ucounts, UCOUNT_RLIMIT_MSGQUEUE, mq_bytes); + spin_unlock(&mq_lock); + put_ucounts(info->ucounts); + info->ucounts = NULL; + /* mqueue_evict_inode() releases info->messages */ + ret = -EMFILE; + goto out_inode; + } + spin_unlock(&mq_lock); + } + } else if (S_ISDIR(mode)) { + inc_nlink(inode); + /* Some things misbehave if size == 0 on a directory */ + inode->i_size = 2 * DIRENT_SIZE; + inode->i_op = &mqueue_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + } + + return inode; +out_inode: + iput(inode); +err: + return ERR_PTR(ret); +} + +static int mqueue_fill_super(struct super_block *sb, struct fs_context *fc) +{ + struct inode *inode; + struct ipc_namespace *ns = sb->s_fs_info; + + sb->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; + sb->s_blocksize = PAGE_SIZE; + sb->s_blocksize_bits = PAGE_SHIFT; + sb->s_magic = MQUEUE_MAGIC; + sb->s_op = &mqueue_super_ops; + sb->s_d_flags = DCACHE_DONTCACHE; + + inode = mqueue_get_inode(sb, ns, S_IFDIR | S_ISVTX | S_IRWXUGO, NULL); + if (IS_ERR(inode)) + return PTR_ERR(inode); + + sb->s_root = d_make_root(inode); + if (!sb->s_root) + return -ENOMEM; + return 0; +} + +static int mqueue_get_tree(struct fs_context *fc) +{ + struct mqueue_fs_context *ctx = fc->fs_private; + + /* + * With a newly created ipc namespace, we don't need to do a search + * for an ipc namespace match, but we still need to set s_fs_info. + */ + if (ctx->newns) { + fc->s_fs_info = ctx->ipc_ns; + return get_tree_nodev(fc, mqueue_fill_super); + } + return get_tree_keyed(fc, mqueue_fill_super, ctx->ipc_ns); +} + +static void mqueue_fs_context_free(struct fs_context *fc) +{ + struct mqueue_fs_context *ctx = fc->fs_private; + + put_ipc_ns(ctx->ipc_ns); + kfree(ctx); +} + +static int mqueue_init_fs_context(struct fs_context *fc) +{ + struct mqueue_fs_context *ctx; + + ctx = kzalloc_obj(struct mqueue_fs_context); + if (!ctx) + return -ENOMEM; + + ctx->ipc_ns = get_ipc_ns(current->nsproxy->ipc_ns); + put_user_ns(fc->user_ns); + fc->user_ns = get_user_ns(ctx->ipc_ns->user_ns); + fc->fs_private = ctx; + fc->ops = &mqueue_fs_context_ops; + return 0; +} + +/* + * mq_init_ns() is currently the only caller of mq_create_mount(). + * So the ns parameter is always a newly created ipc namespace. + */ +static struct vfsmount *mq_create_mount(struct ipc_namespace *ns) +{ + struct mqueue_fs_context *ctx; + struct fs_context *fc; + struct vfsmount *mnt; + + fc = fs_context_for_mount(&mqueue_fs_type, SB_KERNMOUNT); + if (IS_ERR(fc)) + return ERR_CAST(fc); + + ctx = fc->fs_private; + ctx->newns = true; + put_ipc_ns(ctx->ipc_ns); + ctx->ipc_ns = get_ipc_ns(ns); + put_user_ns(fc->user_ns); + fc->user_ns = get_user_ns(ctx->ipc_ns->user_ns); + + mnt = fc_mount_longterm(fc); + put_fs_context(fc); + return mnt; +} + +static void init_once(void *foo) +{ + struct mqueue_inode_info *p = foo; + + inode_init_once(&p->vfs_inode); +} + +static struct inode *mqueue_alloc_inode(struct super_block *sb) +{ + struct mqueue_inode_info *ei; + + ei = alloc_inode_sb(sb, mqueue_inode_cachep, GFP_KERNEL); + if (!ei) + return NULL; + return &ei->vfs_inode; +} + +static void mqueue_free_inode(struct inode *inode) +{ + kmem_cache_free(mqueue_inode_cachep, MQUEUE_I(inode)); +} + +static void mqueue_evict_inode(struct inode *inode) +{ + struct mqueue_inode_info *info; + struct ipc_namespace *ipc_ns; + struct msg_msg *msg, *nmsg; + LIST_HEAD(tmp_msg); + + clear_inode(inode); + + if (S_ISDIR(inode->i_mode)) + return; + + ipc_ns = get_ns_from_inode(inode); + info = MQUEUE_I(inode); + spin_lock(&info->lock); + while ((msg = msg_get(info)) != NULL) + list_add_tail(&msg->m_list, &tmp_msg); + kfree(info->node_cache); + spin_unlock(&info->lock); + + list_for_each_entry_safe(msg, nmsg, &tmp_msg, m_list) { + list_del(&msg->m_list); + free_msg(msg); + } + + if (info->ucounts) { + unsigned long mq_bytes, mq_treesize; + + /* Total amount of bytes accounted for the mqueue */ + mq_treesize = info->attr.mq_maxmsg * sizeof(struct msg_msg) + + min_t(unsigned int, info->attr.mq_maxmsg, MQ_PRIO_MAX) * + sizeof(struct posix_msg_tree_node); + + mq_bytes = mq_treesize + (info->attr.mq_maxmsg * + info->attr.mq_msgsize); + + spin_lock(&mq_lock); + dec_rlimit_ucounts(info->ucounts, UCOUNT_RLIMIT_MSGQUEUE, mq_bytes); + /* + * get_ns_from_inode() ensures that the + * (ipc_ns = sb->s_fs_info) is either a valid ipc_ns + * to which we now hold a reference, or it is NULL. + * We can't put it here under mq_lock, though. + */ + if (ipc_ns) + ipc_ns->mq_queues_count--; + spin_unlock(&mq_lock); + put_ucounts(info->ucounts); + info->ucounts = NULL; + } + if (ipc_ns) + put_ipc_ns(ipc_ns); +} + +static int mqueue_create_attr(struct dentry *dentry, umode_t mode, void *arg) +{ + struct inode *dir = dentry->d_parent->d_inode; + struct inode *inode; + struct mq_attr *attr = arg; + int error; + struct ipc_namespace *ipc_ns; + + spin_lock(&mq_lock); + ipc_ns = __get_ns_from_inode(dir); + if (!ipc_ns) { + error = -EACCES; + goto out_unlock; + } + + if (ipc_ns->mq_queues_count >= ipc_ns->mq_queues_max && + !capable(CAP_SYS_RESOURCE)) { + error = -ENOSPC; + goto out_unlock; + } + ipc_ns->mq_queues_count++; + spin_unlock(&mq_lock); + + inode = mqueue_get_inode(dir->i_sb, ipc_ns, mode, attr); + if (IS_ERR(inode)) { + error = PTR_ERR(inode); + spin_lock(&mq_lock); + ipc_ns->mq_queues_count--; + goto out_unlock; + } + + put_ipc_ns(ipc_ns); + dir->i_size += DIRENT_SIZE; + simple_inode_init_ts(dir); + + d_make_persistent(dentry, inode); + return 0; +out_unlock: + spin_unlock(&mq_lock); + if (ipc_ns) + put_ipc_ns(ipc_ns); + return error; +} + +static int mqueue_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) +{ + return mqueue_create_attr(dentry, mode, NULL); +} + +static int mqueue_unlink(struct inode *dir, struct dentry *dentry) +{ + dir->i_size -= DIRENT_SIZE; + return simple_unlink(dir, dentry); +} + +/* +* This is routine for system read from queue file. +* To avoid mess with doing here some sort of mq_receive we allow +* to read only queue size & notification info (the only values +* that are interesting from user point of view and aren't accessible +* through std routines) +*/ +static ssize_t mqueue_read_file(struct file *filp, char __user *u_data, + size_t count, loff_t *off) +{ + struct inode *inode = file_inode(filp); + struct mqueue_inode_info *info = MQUEUE_I(inode); + char buffer[FILENT_SIZE]; + ssize_t ret; + + spin_lock(&info->lock); + snprintf(buffer, sizeof(buffer), + "QSIZE:%-10lu NOTIFY:%-5d SIGNO:%-5d NOTIFY_PID:%-6d\n", + info->qsize, + info->notify_owner ? info->notify.sigev_notify : 0, + (info->notify_owner && + info->notify.sigev_notify == SIGEV_SIGNAL) ? + info->notify.sigev_signo : 0, + pid_vnr(info->notify_owner)); + spin_unlock(&info->lock); + buffer[sizeof(buffer)-1] = '\0'; + + ret = simple_read_from_buffer(u_data, count, off, buffer, + strlen(buffer)); + if (ret <= 0) + return ret; + + inode_set_atime_to_ts(inode, inode_set_ctime_current(inode)); + return ret; +} + +static int mqueue_flush_file(struct file *filp, fl_owner_t id) +{ + struct mqueue_inode_info *info = MQUEUE_I(file_inode(filp)); + + spin_lock(&info->lock); + if (task_tgid(current) == info->notify_owner) + remove_notification(info); + + spin_unlock(&info->lock); + return 0; +} + +static __poll_t mqueue_poll_file(struct file *filp, struct poll_table_struct *poll_tab) +{ + struct mqueue_inode_info *info = MQUEUE_I(file_inode(filp)); + __poll_t retval = 0; + + poll_wait(filp, &info->wait_q, poll_tab); + + spin_lock(&info->lock); + if (info->attr.mq_curmsgs) + retval = EPOLLIN | EPOLLRDNORM; + + if (info->attr.mq_curmsgs < info->attr.mq_maxmsg) + retval |= EPOLLOUT | EPOLLWRNORM; + spin_unlock(&info->lock); + + return retval; +} + +/* Adds current to info->e_wait_q[sr] before element with smaller prio */ +static void wq_add(struct mqueue_inode_info *info, int sr, + struct ext_wait_queue *ewp) +{ + struct ext_wait_queue *walk; + + list_for_each_entry(walk, &info->e_wait_q[sr].list, list) { + if (walk->task->prio <= current->prio) { + list_add_tail(&ewp->list, &walk->list); + return; + } + } + list_add_tail(&ewp->list, &info->e_wait_q[sr].list); +} + +/* + * Puts current task to sleep. Caller must hold queue lock. After return + * lock isn't held. + * sr: SEND or RECV + */ +static int wq_sleep(struct mqueue_inode_info *info, int sr, + ktime_t *timeout, struct ext_wait_queue *ewp) + __releases(&info->lock) +{ + int retval; + signed long time; + + wq_add(info, sr, ewp); + + for (;;) { + /* memory barrier not required, we hold info->lock */ + __set_current_state(TASK_INTERRUPTIBLE); + + spin_unlock(&info->lock); + time = schedule_hrtimeout_range_clock(timeout, 0, + HRTIMER_MODE_ABS, CLOCK_REALTIME); + + if (READ_ONCE(ewp->state) == STATE_READY) { + /* see MQ_BARRIER for purpose/pairing */ + smp_acquire__after_ctrl_dep(); + retval = 0; + goto out; + } + spin_lock(&info->lock); + + /* we hold info->lock, so no memory barrier required */ + if (READ_ONCE(ewp->state) == STATE_READY) { + retval = 0; + goto out_unlock; + } + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + if (time == 0) { + retval = -ETIMEDOUT; + break; + } + } + list_del(&ewp->list); +out_unlock: + spin_unlock(&info->lock); +out: + return retval; +} + +/* + * Returns waiting task that should be serviced first or NULL if none exists + */ +static struct ext_wait_queue *wq_get_first_waiter( + struct mqueue_inode_info *info, int sr) +{ + struct list_head *ptr; + + ptr = info->e_wait_q[sr].list.prev; + if (ptr == &info->e_wait_q[sr].list) + return NULL; + return list_entry(ptr, struct ext_wait_queue, list); +} + + +static inline void set_cookie(struct sk_buff *skb, char code) +{ + ((char *)skb->data)[NOTIFY_COOKIE_LEN-1] = code; +} + +/* + * The next function is only to split too long sys_mq_timedsend + */ +static void __do_notify(struct mqueue_inode_info *info) +{ + /* notification + * invoked when there is registered process and there isn't process + * waiting synchronously for message AND state of queue changed from + * empty to not empty. Here we are sure that no one is waiting + * synchronously. */ + if (info->notify_owner && + info->attr.mq_curmsgs == 1) { + switch (info->notify.sigev_notify) { + case SIGEV_NONE: + break; + case SIGEV_SIGNAL: { + struct kernel_siginfo sig_i; + struct task_struct *task; + + if (!info->notify.sigev_signo) + break; + + clear_siginfo(&sig_i); + sig_i.si_signo = info->notify.sigev_signo; + sig_i.si_errno = 0; + sig_i.si_code = SI_MESGQ; + sig_i.si_value = info->notify.sigev_value; + rcu_read_lock(); + /* map current pid/uid into info->owner's namespaces */ + sig_i.si_pid = task_tgid_nr_ns(current, + ns_of_pid(info->notify_owner)); + sig_i.si_uid = from_kuid_munged(info->notify_user_ns, + current_uid()); + /* + * We can't use kill_pid_info(), this signal should + * bypass check_kill_permission(). It is from kernel + * but si_fromuser() can't know this. + * We do check the self_exec_id, to avoid sending + * signals to programs that don't expect them. + */ + task = pid_task(info->notify_owner, PIDTYPE_TGID); + if (task && task->self_exec_id == + info->notify_self_exec_id) { + do_send_sig_info(info->notify.sigev_signo, + &sig_i, task, PIDTYPE_TGID); + } + rcu_read_unlock(); + break; + } + case SIGEV_THREAD: + set_cookie(info->notify_cookie, NOTIFY_WOKENUP); + netlink_sendskb(info->notify_sock, info->notify_cookie); + break; + } + /* after notification unregisters process */ + put_pid(info->notify_owner); + put_user_ns(info->notify_user_ns); + info->notify_owner = NULL; + info->notify_user_ns = NULL; + } + wake_up(&info->wait_q); +} + +static int prepare_timeout(const struct __kernel_timespec __user *u_abs_timeout, + struct timespec64 *ts) +{ + if (get_timespec64(ts, u_abs_timeout)) + return -EFAULT; + if (!timespec64_valid(ts)) + return -EINVAL; + return 0; +} + +static void remove_notification(struct mqueue_inode_info *info) +{ + if (info->notify_owner != NULL && + info->notify.sigev_notify == SIGEV_THREAD) { + set_cookie(info->notify_cookie, NOTIFY_REMOVED); + netlink_sendskb(info->notify_sock, info->notify_cookie); + } + put_pid(info->notify_owner); + put_user_ns(info->notify_user_ns); + info->notify_owner = NULL; + info->notify_user_ns = NULL; +} + +static int prepare_open(struct dentry *dentry, int oflag, int ro, + umode_t mode, struct filename *name, + struct mq_attr *attr) +{ + static const int oflag2acc[O_ACCMODE] = { MAY_READ, MAY_WRITE, + MAY_READ | MAY_WRITE }; + int acc; + + if (d_really_is_negative(dentry)) { + if (!(oflag & O_CREAT)) + return -ENOENT; + if (ro) + return ro; + audit_inode_parent_hidden(name, dentry->d_parent); + return vfs_mkobj(dentry, mode & ~current_umask(), + mqueue_create_attr, attr); + } + /* it already existed */ + audit_inode(name, dentry, 0); + if ((oflag & (O_CREAT|O_EXCL)) == (O_CREAT|O_EXCL)) + return -EEXIST; + if ((oflag & O_ACCMODE) == (O_RDWR | O_WRONLY)) + return -EINVAL; + acc = oflag2acc[oflag & O_ACCMODE]; + return inode_permission(&nop_mnt_idmap, d_inode(dentry), acc); +} + +static struct file *mqueue_file_open(struct filename *name, + struct vfsmount *mnt, int oflag, int ro, + umode_t mode, struct mq_attr *attr) +{ + struct dentry *dentry; + struct file *file; + int ret; + + dentry = start_creating_noperm(mnt->mnt_root, &QSTR(name->name)); + if (IS_ERR(dentry)) + return ERR_CAST(dentry); + + ret = prepare_open(dentry, oflag, ro, mode, name, attr); + file = ERR_PTR(ret); + if (!ret) { + const struct path path = { .mnt = mnt, .dentry = dentry }; + file = dentry_open(&path, oflag, current_cred()); + } + + end_creating(dentry); + return file; +} + +static int do_mq_open(const char __user *u_name, int oflag, umode_t mode, + struct mq_attr *attr) +{ + struct vfsmount *mnt = current->nsproxy->ipc_ns->mq_mnt; + int fd, ro; + + audit_mq_open(oflag, mode, attr); + + CLASS(filename, name)(u_name); + if (IS_ERR(name)) + return PTR_ERR(name); + + ro = mnt_want_write(mnt); /* we'll drop it in any case */ + fd = FD_ADD(O_CLOEXEC, mqueue_file_open(name, mnt, oflag, ro, mode, attr)); + if (!ro) + mnt_drop_write(mnt); + return fd; +} + +SYSCALL_DEFINE4(mq_open, const char __user *, u_name, int, oflag, umode_t, mode, + struct mq_attr __user *, u_attr) +{ + struct mq_attr attr; + if (u_attr && copy_from_user(&attr, u_attr, sizeof(struct mq_attr))) + return -EFAULT; + + return do_mq_open(u_name, oflag, mode, u_attr ? &attr : NULL); +} + +SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name) +{ + int err; + struct dentry *dentry; + struct inode *inode; + struct ipc_namespace *ipc_ns = current->nsproxy->ipc_ns; + struct vfsmount *mnt = ipc_ns->mq_mnt; + CLASS(filename, name)(u_name); + + if (IS_ERR(name)) + return PTR_ERR(name); + + audit_inode_parent_hidden(name, mnt->mnt_root); + err = mnt_want_write(mnt); + if (err) + return err; + dentry = start_removing_noperm(mnt->mnt_root, &QSTR(name->name)); + if (IS_ERR(dentry)) { + err = PTR_ERR(dentry); + goto out_drop_write; + } + + inode = d_inode(dentry); + ihold(inode); + err = vfs_unlink(&nop_mnt_idmap, d_inode(mnt->mnt_root), + dentry, NULL); + end_removing(dentry); + iput(inode); + +out_drop_write: + mnt_drop_write(mnt); + return err; +} + +/* Pipelined send and receive functions. + * + * If a receiver finds no waiting message, then it registers itself in the + * list of waiting receivers. A sender checks that list before adding the new + * message into the message array. If there is a waiting receiver, then it + * bypasses the message array and directly hands the message over to the + * receiver. The receiver accepts the message and returns without grabbing the + * queue spinlock: + * + * - Set pointer to message. + * - Queue the receiver task for later wakeup (without the info->lock). + * - Update its state to STATE_READY. Now the receiver can continue. + * - Wake up the process after the lock is dropped. Should the process wake up + * before this wakeup (due to a timeout or a signal) it will either see + * STATE_READY and continue or acquire the lock to check the state again. + * + * The same algorithm is used for senders. + */ + +static inline void __pipelined_op(struct wake_q_head *wake_q, + struct mqueue_inode_info *info, + struct ext_wait_queue *this) +{ + struct task_struct *task; + + list_del(&this->list); + task = get_task_struct(this->task); + + /* see MQ_BARRIER for purpose/pairing */ + smp_store_release(&this->state, STATE_READY); + wake_q_add_safe(wake_q, task); +} + +/* pipelined_send() - send a message directly to the task waiting in + * sys_mq_timedreceive() (without inserting message into a queue). + */ +static inline void pipelined_send(struct wake_q_head *wake_q, + struct mqueue_inode_info *info, + struct msg_msg *message, + struct ext_wait_queue *receiver) +{ + receiver->msg = message; + __pipelined_op(wake_q, info, receiver); +} + +/* pipelined_receive() - if there is task waiting in sys_mq_timedsend() + * gets its message and put to the queue (we have one free place for sure). */ +static inline void pipelined_receive(struct wake_q_head *wake_q, + struct mqueue_inode_info *info) +{ + struct ext_wait_queue *sender = wq_get_first_waiter(info, SEND); + + if (!sender) { + /* for poll */ + wake_up_interruptible(&info->wait_q); + return; + } + if (msg_insert(sender->msg, info)) + return; + + __pipelined_op(wake_q, info, sender); +} + +static int do_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr, + size_t msg_len, unsigned int msg_prio, + struct timespec64 *ts) +{ + struct inode *inode; + struct ext_wait_queue wait; + struct ext_wait_queue *receiver; + struct msg_msg *msg_ptr; + struct mqueue_inode_info *info; + ktime_t expires, *timeout = NULL; + struct posix_msg_tree_node *new_leaf = NULL; + int ret = 0; + DEFINE_WAKE_Q(wake_q); + + if (unlikely(msg_prio >= (unsigned long) MQ_PRIO_MAX)) + return -EINVAL; + + if (ts) { + expires = timespec64_to_ktime(*ts); + timeout = &expires; + } + + audit_mq_sendrecv(mqdes, msg_len, msg_prio, ts); + + CLASS(fd, f)(mqdes); + if (fd_empty(f)) + return -EBADF; + + inode = file_inode(fd_file(f)); + if (unlikely(fd_file(f)->f_op != &mqueue_file_operations)) + return -EBADF; + info = MQUEUE_I(inode); + audit_file(fd_file(f)); + + if (unlikely(!(fd_file(f)->f_mode & FMODE_WRITE))) + return -EBADF; + + if (unlikely(msg_len > info->attr.mq_msgsize)) + return -EMSGSIZE; + + /* First try to allocate memory, before doing anything with + * existing queues. */ + msg_ptr = load_msg(u_msg_ptr, msg_len); + if (IS_ERR(msg_ptr)) + return PTR_ERR(msg_ptr); + msg_ptr->m_ts = msg_len; + msg_ptr->m_type = msg_prio; + + /* + * msg_insert really wants us to have a valid, spare node struct so + * it doesn't have to kmalloc a GFP_ATOMIC allocation, but it will + * fall back to that if necessary. + */ + if (!info->node_cache) + new_leaf = kmalloc_obj(*new_leaf); + + spin_lock(&info->lock); + + if (!info->node_cache && new_leaf) { + /* Save our speculative allocation into the cache */ + INIT_LIST_HEAD(&new_leaf->msg_list); + info->node_cache = new_leaf; + new_leaf = NULL; + } else { + kfree(new_leaf); + } + + if (info->attr.mq_curmsgs == info->attr.mq_maxmsg) { + if (fd_file(f)->f_flags & O_NONBLOCK) { + ret = -EAGAIN; + } else { + wait.task = current; + wait.msg = (void *) msg_ptr; + + /* memory barrier not required, we hold info->lock */ + WRITE_ONCE(wait.state, STATE_NONE); + ret = wq_sleep(info, SEND, timeout, &wait); + /* + * wq_sleep must be called with info->lock held, and + * returns with the lock released + */ + goto out_free; + } + } else { + receiver = wq_get_first_waiter(info, RECV); + if (receiver) { + pipelined_send(&wake_q, info, msg_ptr, receiver); + } else { + /* adds message to the queue */ + ret = msg_insert(msg_ptr, info); + if (ret) + goto out_unlock; + __do_notify(info); + } + simple_inode_init_ts(inode); + } +out_unlock: + spin_unlock(&info->lock); + wake_up_q(&wake_q); +out_free: + if (ret) + free_msg(msg_ptr); + return ret; +} + +static int do_mq_timedreceive(mqd_t mqdes, char __user *u_msg_ptr, + size_t msg_len, unsigned int __user *u_msg_prio, + struct timespec64 *ts) +{ + ssize_t ret; + struct msg_msg *msg_ptr; + struct inode *inode; + struct mqueue_inode_info *info; + struct ext_wait_queue wait; + ktime_t expires, *timeout = NULL; + struct posix_msg_tree_node *new_leaf = NULL; + + if (ts) { + expires = timespec64_to_ktime(*ts); + timeout = &expires; + } + + audit_mq_sendrecv(mqdes, msg_len, 0, ts); + + CLASS(fd, f)(mqdes); + if (fd_empty(f)) + return -EBADF; + + inode = file_inode(fd_file(f)); + if (unlikely(fd_file(f)->f_op != &mqueue_file_operations)) + return -EBADF; + info = MQUEUE_I(inode); + audit_file(fd_file(f)); + + if (unlikely(!(fd_file(f)->f_mode & FMODE_READ))) + return -EBADF; + + /* checks if buffer is big enough */ + if (unlikely(msg_len < info->attr.mq_msgsize)) + return -EMSGSIZE; + + /* + * msg_insert really wants us to have a valid, spare node struct so + * it doesn't have to kmalloc a GFP_ATOMIC allocation, but it will + * fall back to that if necessary. + */ + if (!info->node_cache) + new_leaf = kmalloc_obj(*new_leaf); + + spin_lock(&info->lock); + + if (!info->node_cache && new_leaf) { + /* Save our speculative allocation into the cache */ + INIT_LIST_HEAD(&new_leaf->msg_list); + info->node_cache = new_leaf; + } else { + kfree(new_leaf); + } + + if (info->attr.mq_curmsgs == 0) { + if (fd_file(f)->f_flags & O_NONBLOCK) { + spin_unlock(&info->lock); + ret = -EAGAIN; + } else { + wait.task = current; + + /* memory barrier not required, we hold info->lock */ + WRITE_ONCE(wait.state, STATE_NONE); + ret = wq_sleep(info, RECV, timeout, &wait); + msg_ptr = wait.msg; + } + } else { + DEFINE_WAKE_Q(wake_q); + + msg_ptr = msg_get(info); + + simple_inode_init_ts(inode); + + /* There is now free space in queue. */ + pipelined_receive(&wake_q, info); + spin_unlock(&info->lock); + wake_up_q(&wake_q); + ret = 0; + } + if (ret == 0) { + ret = msg_ptr->m_ts; + + if ((u_msg_prio && put_user(msg_ptr->m_type, u_msg_prio)) || + store_msg(u_msg_ptr, msg_ptr, msg_ptr->m_ts)) { + ret = -EFAULT; + } + free_msg(msg_ptr); + } + return ret; +} + +SYSCALL_DEFINE5(mq_timedsend, mqd_t, mqdes, const char __user *, u_msg_ptr, + size_t, msg_len, unsigned int, msg_prio, + const struct __kernel_timespec __user *, u_abs_timeout) +{ + struct timespec64 ts, *p = NULL; + if (u_abs_timeout) { + int res = prepare_timeout(u_abs_timeout, &ts); + if (res) + return res; + p = &ts; + } + return do_mq_timedsend(mqdes, u_msg_ptr, msg_len, msg_prio, p); +} + +SYSCALL_DEFINE5(mq_timedreceive, mqd_t, mqdes, char __user *, u_msg_ptr, + size_t, msg_len, unsigned int __user *, u_msg_prio, + const struct __kernel_timespec __user *, u_abs_timeout) +{ + struct timespec64 ts, *p = NULL; + if (u_abs_timeout) { + int res = prepare_timeout(u_abs_timeout, &ts); + if (res) + return res; + p = &ts; + } + return do_mq_timedreceive(mqdes, u_msg_ptr, msg_len, u_msg_prio, p); +} + +/* + * Notes: the case when user wants us to deregister (with NULL as pointer) + * and he isn't currently owner of notification, will be silently discarded. + * It isn't explicitly defined in the POSIX. + */ +static int do_mq_notify(mqd_t mqdes, const struct sigevent *notification) +{ + int ret; + struct sock *sock; + struct inode *inode; + struct mqueue_inode_info *info; + struct sk_buff *nc; + + audit_mq_notify(mqdes, notification); + + nc = NULL; + sock = NULL; + if (notification != NULL) { + if (unlikely(notification->sigev_notify != SIGEV_NONE && + notification->sigev_notify != SIGEV_SIGNAL && + notification->sigev_notify != SIGEV_THREAD)) + return -EINVAL; + if (notification->sigev_notify == SIGEV_SIGNAL && + (!notification->sigev_signo || + !valid_signal(notification->sigev_signo))) + return -EINVAL; + if (notification->sigev_notify == SIGEV_THREAD) { + long timeo; + + /* create the notify skb */ + nc = alloc_skb(NOTIFY_COOKIE_LEN, GFP_KERNEL); + if (!nc) + return -ENOMEM; + + if (copy_from_user(nc->data, + notification->sigev_value.sival_ptr, + NOTIFY_COOKIE_LEN)) { + kfree_skb(nc); + return -EFAULT; + } + + /* TODO: add a header? */ + skb_put(nc, NOTIFY_COOKIE_LEN); + /* and attach it to the socket */ +retry: + sock = netlink_getsockbyfd(notification->sigev_signo); + if (IS_ERR(sock)) { + kfree_skb(nc); + return PTR_ERR(sock); + } + + timeo = MAX_SCHEDULE_TIMEOUT; + ret = netlink_attachskb(sock, nc, &timeo, NULL); + if (ret == 1) + goto retry; + if (ret) + return ret; + } + } + + CLASS(fd, f)(mqdes); + if (fd_empty(f)) { + ret = -EBADF; + goto out; + } + + inode = file_inode(fd_file(f)); + if (unlikely(fd_file(f)->f_op != &mqueue_file_operations)) { + ret = -EBADF; + goto out; + } + info = MQUEUE_I(inode); + + ret = 0; + spin_lock(&info->lock); + if (notification == NULL) { + if (info->notify_owner == task_tgid(current)) { + remove_notification(info); + inode_set_atime_to_ts(inode, + inode_set_ctime_current(inode)); + } + } else if (info->notify_owner != NULL) { + ret = -EBUSY; + } else { + switch (notification->sigev_notify) { + case SIGEV_NONE: + info->notify.sigev_notify = SIGEV_NONE; + break; + case SIGEV_THREAD: + info->notify_sock = sock; + info->notify_cookie = nc; + sock = NULL; + nc = NULL; + info->notify.sigev_notify = SIGEV_THREAD; + break; + case SIGEV_SIGNAL: + info->notify.sigev_signo = notification->sigev_signo; + info->notify.sigev_value = notification->sigev_value; + info->notify.sigev_notify = SIGEV_SIGNAL; + info->notify_self_exec_id = current->self_exec_id; + break; + } + + info->notify_owner = get_pid(task_tgid(current)); + info->notify_user_ns = get_user_ns(current_user_ns()); + inode_set_atime_to_ts(inode, inode_set_ctime_current(inode)); + } + spin_unlock(&info->lock); +out: + if (sock) + netlink_detachskb(sock, nc); + return ret; +} + +SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes, + const struct sigevent __user *, u_notification) +{ + struct sigevent n, *p = NULL; + if (u_notification) { + if (copy_from_user(&n, u_notification, sizeof(struct sigevent))) + return -EFAULT; + p = &n; + } + return do_mq_notify(mqdes, p); +} + +static int do_mq_getsetattr(int mqdes, struct mq_attr *new, struct mq_attr *old) +{ + struct inode *inode; + struct mqueue_inode_info *info; + + if (new && (new->mq_flags & (~O_NONBLOCK))) + return -EINVAL; + + CLASS(fd, f)(mqdes); + if (fd_empty(f)) + return -EBADF; + + if (unlikely(fd_file(f)->f_op != &mqueue_file_operations)) + return -EBADF; + + inode = file_inode(fd_file(f)); + info = MQUEUE_I(inode); + + spin_lock(&info->lock); + + if (old) { + *old = info->attr; + old->mq_flags = fd_file(f)->f_flags & O_NONBLOCK; + } + if (new) { + audit_mq_getsetattr(mqdes, new); + spin_lock(&fd_file(f)->f_lock); + if (new->mq_flags & O_NONBLOCK) + fd_file(f)->f_flags |= O_NONBLOCK; + else + fd_file(f)->f_flags &= ~O_NONBLOCK; + spin_unlock(&fd_file(f)->f_lock); + + inode_set_atime_to_ts(inode, inode_set_ctime_current(inode)); + } + + spin_unlock(&info->lock); + return 0; +} + +SYSCALL_DEFINE3(mq_getsetattr, mqd_t, mqdes, + const struct mq_attr __user *, u_mqstat, + struct mq_attr __user *, u_omqstat) +{ + int ret; + struct mq_attr mqstat, omqstat; + struct mq_attr *new = NULL, *old = NULL; + + if (u_mqstat) { + new = &mqstat; + if (copy_from_user(new, u_mqstat, sizeof(struct mq_attr))) + return -EFAULT; + } + if (u_omqstat) + old = &omqstat; + + ret = do_mq_getsetattr(mqdes, new, old); + if (ret || !old) + return ret; + + if (copy_to_user(u_omqstat, old, sizeof(struct mq_attr))) + return -EFAULT; + return 0; +} + +#ifdef CONFIG_COMPAT + +struct compat_mq_attr { + compat_long_t mq_flags; /* message queue flags */ + compat_long_t mq_maxmsg; /* maximum number of messages */ + compat_long_t mq_msgsize; /* maximum message size */ + compat_long_t mq_curmsgs; /* number of messages currently queued */ + compat_long_t __reserved[4]; /* ignored for input, zeroed for output */ +}; + +static inline int get_compat_mq_attr(struct mq_attr *attr, + const struct compat_mq_attr __user *uattr) +{ + struct compat_mq_attr v; + + if (copy_from_user(&v, uattr, sizeof(*uattr))) + return -EFAULT; + + memset(attr, 0, sizeof(*attr)); + attr->mq_flags = v.mq_flags; + attr->mq_maxmsg = v.mq_maxmsg; + attr->mq_msgsize = v.mq_msgsize; + attr->mq_curmsgs = v.mq_curmsgs; + return 0; +} + +static inline int put_compat_mq_attr(const struct mq_attr *attr, + struct compat_mq_attr __user *uattr) +{ + struct compat_mq_attr v; + + memset(&v, 0, sizeof(v)); + v.mq_flags = attr->mq_flags; + v.mq_maxmsg = attr->mq_maxmsg; + v.mq_msgsize = attr->mq_msgsize; + v.mq_curmsgs = attr->mq_curmsgs; + if (copy_to_user(uattr, &v, sizeof(*uattr))) + return -EFAULT; + return 0; +} + +COMPAT_SYSCALL_DEFINE4(mq_open, const char __user *, u_name, + int, oflag, compat_mode_t, mode, + struct compat_mq_attr __user *, u_attr) +{ + struct mq_attr attr, *p = NULL; + if (u_attr && oflag & O_CREAT) { + p = &attr; + if (get_compat_mq_attr(&attr, u_attr)) + return -EFAULT; + } + return do_mq_open(u_name, oflag, mode, p); +} + +COMPAT_SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes, + const struct compat_sigevent __user *, u_notification) +{ + struct sigevent n, *p = NULL; + if (u_notification) { + if (get_compat_sigevent(&n, u_notification)) + return -EFAULT; + if (n.sigev_notify == SIGEV_THREAD) + n.sigev_value.sival_ptr = compat_ptr(n.sigev_value.sival_int); + p = &n; + } + return do_mq_notify(mqdes, p); +} + +COMPAT_SYSCALL_DEFINE3(mq_getsetattr, mqd_t, mqdes, + const struct compat_mq_attr __user *, u_mqstat, + struct compat_mq_attr __user *, u_omqstat) +{ + int ret; + struct mq_attr mqstat, omqstat; + struct mq_attr *new = NULL, *old = NULL; + + if (u_mqstat) { + new = &mqstat; + if (get_compat_mq_attr(new, u_mqstat)) + return -EFAULT; + } + if (u_omqstat) + old = &omqstat; + + ret = do_mq_getsetattr(mqdes, new, old); + if (ret || !old) + return ret; + + if (put_compat_mq_attr(old, u_omqstat)) + return -EFAULT; + return 0; +} +#endif + +#ifdef CONFIG_COMPAT_32BIT_TIME +static int compat_prepare_timeout(const struct old_timespec32 __user *p, + struct timespec64 *ts) +{ + if (get_old_timespec32(ts, p)) + return -EFAULT; + if (!timespec64_valid(ts)) + return -EINVAL; + return 0; +} + +SYSCALL_DEFINE5(mq_timedsend_time32, mqd_t, mqdes, + const char __user *, u_msg_ptr, + unsigned int, msg_len, unsigned int, msg_prio, + const struct old_timespec32 __user *, u_abs_timeout) +{ + struct timespec64 ts, *p = NULL; + if (u_abs_timeout) { + int res = compat_prepare_timeout(u_abs_timeout, &ts); + if (res) + return res; + p = &ts; + } + return do_mq_timedsend(mqdes, u_msg_ptr, msg_len, msg_prio, p); +} + +SYSCALL_DEFINE5(mq_timedreceive_time32, mqd_t, mqdes, + char __user *, u_msg_ptr, + unsigned int, msg_len, unsigned int __user *, u_msg_prio, + const struct old_timespec32 __user *, u_abs_timeout) +{ + struct timespec64 ts, *p = NULL; + if (u_abs_timeout) { + int res = compat_prepare_timeout(u_abs_timeout, &ts); + if (res) + return res; + p = &ts; + } + return do_mq_timedreceive(mqdes, u_msg_ptr, msg_len, u_msg_prio, p); +} +#endif + +static const struct inode_operations mqueue_dir_inode_operations = { + .lookup = simple_lookup, + .create = mqueue_create, + .unlink = mqueue_unlink, +}; + +static const struct file_operations mqueue_file_operations = { + .flush = mqueue_flush_file, + .poll = mqueue_poll_file, + .read = mqueue_read_file, + .llseek = default_llseek, +}; + +static const struct super_operations mqueue_super_ops = { + .alloc_inode = mqueue_alloc_inode, + .free_inode = mqueue_free_inode, + .evict_inode = mqueue_evict_inode, + .statfs = simple_statfs, +}; + +static const struct fs_context_operations mqueue_fs_context_ops = { + .free = mqueue_fs_context_free, + .get_tree = mqueue_get_tree, +}; + +static struct file_system_type mqueue_fs_type = { + .name = "mqueue", + .init_fs_context = mqueue_init_fs_context, + .kill_sb = kill_anon_super, + .fs_flags = FS_USERNS_MOUNT, +}; + +int mq_init_ns(struct ipc_namespace *ns) +{ + struct vfsmount *m; + + ns->mq_queues_count = 0; + ns->mq_queues_max = DFLT_QUEUESMAX; + ns->mq_msg_max = DFLT_MSGMAX; + ns->mq_msgsize_max = DFLT_MSGSIZEMAX; + ns->mq_msg_default = DFLT_MSG; + ns->mq_msgsize_default = DFLT_MSGSIZE; + + m = mq_create_mount(ns); + if (IS_ERR(m)) + return PTR_ERR(m); + ns->mq_mnt = m; + return 0; +} + +void mq_clear_sbinfo(struct ipc_namespace *ns) +{ + ns->mq_mnt->mnt_sb->s_fs_info = NULL; +} + +static int __init init_mqueue_fs(void) +{ + int error; + + mqueue_inode_cachep = kmem_cache_create("mqueue_inode_cache", + sizeof(struct mqueue_inode_info), 0, + SLAB_HWCACHE_ALIGN|SLAB_ACCOUNT, init_once); + if (mqueue_inode_cachep == NULL) + return -ENOMEM; + + if (!setup_mq_sysctls(&init_ipc_ns)) { + pr_warn("sysctl registration failed\n"); + error = -ENOMEM; + goto out_kmem; + } + + error = register_filesystem(&mqueue_fs_type); + if (error) + goto out_sysctl; + + spin_lock_init(&mq_lock); + + error = mq_init_ns(&init_ipc_ns); + if (error) + goto out_filesystem; + + return 0; + +out_filesystem: + unregister_filesystem(&mqueue_fs_type); +out_sysctl: + retire_mq_sysctls(&init_ipc_ns); +out_kmem: + kmem_cache_destroy(mqueue_inode_cachep); + return error; +} + +device_initcall(init_mqueue_fs); diff --git a/ipc/msg.c b/ipc/msg.c new file mode 100644 index 000000000..62996b97f --- /dev/null +++ b/ipc/msg.c @@ -0,0 +1,1376 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/ipc/msg.c + * Copyright (C) 1992 Krishna Balasubramanian + * + * Removed all the remaining kerneld mess + * Catch the -EFAULT stuff properly + * Use GFP_KERNEL for messages as in 1.2 + * Fixed up the unchecked user space derefs + * Copyright (C) 1998 Alan Cox & Andi Kleen + * + * /proc/sysvipc/msg support (c) 1999 Dragos Acostachioaie <dragos@iname.com> + * + * mostly rewritten, threaded and wake-one semantics added + * MSGMAX limit removed, sysctl's added + * (c) 1999 Manfred Spraul <manfred@colorfullife.com> + * + * support for audit of ipc object properties and permission changes + * Dustin Kirkland <dustin.kirkland@us.ibm.com> + * + * namespaces support + * OpenVZ, SWsoft Inc. + * Pavel Emelianov <xemul@openvz.org> + */ + +#include <linux/capability.h> +#include <linux/msg.h> +#include <linux/spinlock.h> +#include <linux/init.h> +#include <linux/mm.h> +#include <linux/proc_fs.h> +#include <linux/list.h> +#include <linux/security.h> +#include <linux/sched/wake_q.h> +#include <linux/syscalls.h> +#include <linux/audit.h> +#include <linux/seq_file.h> +#include <linux/rwsem.h> +#include <linux/nsproxy.h> +#include <linux/ipc_namespace.h> +#include <linux/rhashtable.h> +#include <linux/percpu_counter.h> + +#include <asm/current.h> +#include <linux/uaccess.h> +#include "util.h" + +/* one msq_queue structure for each present queue on the system */ +struct msg_queue { + struct kern_ipc_perm q_perm; + time64_t q_stime; /* last msgsnd time */ + time64_t q_rtime; /* last msgrcv time */ + time64_t q_ctime; /* last change time */ + unsigned long q_cbytes; /* current number of bytes on queue */ + unsigned long q_qnum; /* number of messages in queue */ + unsigned long q_qbytes; /* max number of bytes on queue */ + struct pid *q_lspid; /* pid of last msgsnd */ + struct pid *q_lrpid; /* last receive pid */ + + struct list_head q_messages; + struct list_head q_receivers; + struct list_head q_senders; +} __randomize_layout; + +/* + * MSG_BARRIER Locking: + * + * Similar to the optimization used in ipc/mqueue.c, one syscall return path + * does not acquire any locks when it sees that a message exists in + * msg_receiver.r_msg. Therefore r_msg is set using smp_store_release() + * and accessed using READ_ONCE()+smp_acquire__after_ctrl_dep(). In addition, + * wake_q_add_safe() is used. See ipc/mqueue.c for more details + */ + +/* one msg_receiver structure for each sleeping receiver */ +struct msg_receiver { + struct list_head r_list; + struct task_struct *r_tsk; + + int r_mode; + long r_msgtype; + long r_maxsize; + + struct msg_msg *r_msg; +}; + +/* one msg_sender for each sleeping sender */ +struct msg_sender { + struct list_head list; + struct task_struct *tsk; + size_t msgsz; +}; + +#define SEARCH_ANY 1 +#define SEARCH_EQUAL 2 +#define SEARCH_NOTEQUAL 3 +#define SEARCH_LESSEQUAL 4 +#define SEARCH_NUMBER 5 + +#define msg_ids(ns) ((ns)->ids[IPC_MSG_IDS]) + +static inline struct msg_queue *msq_obtain_object(struct ipc_namespace *ns, int id) +{ + struct kern_ipc_perm *ipcp = ipc_obtain_object_idr(&msg_ids(ns), id); + + if (IS_ERR(ipcp)) + return ERR_CAST(ipcp); + + return container_of(ipcp, struct msg_queue, q_perm); +} + +static inline struct msg_queue *msq_obtain_object_check(struct ipc_namespace *ns, + int id) +{ + struct kern_ipc_perm *ipcp = ipc_obtain_object_check(&msg_ids(ns), id); + + if (IS_ERR(ipcp)) + return ERR_CAST(ipcp); + + return container_of(ipcp, struct msg_queue, q_perm); +} + +static inline void msg_rmid(struct ipc_namespace *ns, struct msg_queue *s) +{ + ipc_rmid(&msg_ids(ns), &s->q_perm); +} + +static void msg_rcu_free(struct rcu_head *head) +{ + struct kern_ipc_perm *p = container_of(head, struct kern_ipc_perm, rcu); + struct msg_queue *msq = container_of(p, struct msg_queue, q_perm); + + security_msg_queue_free(&msq->q_perm); + kfree(msq); +} + +/** + * newque - Create a new msg queue + * @ns: namespace + * @params: ptr to the structure that contains the key and msgflg + * + * Called with msg_ids.rwsem held (writer) + */ +static int newque(struct ipc_namespace *ns, struct ipc_params *params) +{ + struct msg_queue *msq; + int retval; + key_t key = params->key; + int msgflg = params->flg; + + msq = kmalloc_obj(*msq, GFP_KERNEL_ACCOUNT); + if (unlikely(!msq)) + return -ENOMEM; + + msq->q_perm.mode = msgflg & S_IRWXUGO; + msq->q_perm.key = key; + + msq->q_perm.security = NULL; + retval = security_msg_queue_alloc(&msq->q_perm); + if (retval) { + kfree(msq); + return retval; + } + + msq->q_stime = msq->q_rtime = 0; + msq->q_ctime = ktime_get_real_seconds(); + msq->q_cbytes = msq->q_qnum = 0; + msq->q_qbytes = ns->msg_ctlmnb; + msq->q_lspid = msq->q_lrpid = NULL; + INIT_LIST_HEAD(&msq->q_messages); + INIT_LIST_HEAD(&msq->q_receivers); + INIT_LIST_HEAD(&msq->q_senders); + + /* ipc_addid() locks msq upon success. */ + retval = ipc_addid(&msg_ids(ns), &msq->q_perm, ns->msg_ctlmni); + if (retval < 0) { + ipc_rcu_putref(&msq->q_perm, msg_rcu_free); + return retval; + } + + ipc_unlock_object(&msq->q_perm); + rcu_read_unlock(); + + return msq->q_perm.id; +} + +static inline bool msg_fits_inqueue(struct msg_queue *msq, size_t msgsz) +{ + return msgsz + msq->q_cbytes <= msq->q_qbytes && + 1 + msq->q_qnum <= msq->q_qbytes; +} + +static inline void ss_add(struct msg_queue *msq, + struct msg_sender *mss, size_t msgsz) +{ + mss->tsk = current; + mss->msgsz = msgsz; + /* + * No memory barrier required: we did ipc_lock_object(), + * and the waker obtains that lock before calling wake_q_add(). + */ + __set_current_state(TASK_INTERRUPTIBLE); + list_add_tail(&mss->list, &msq->q_senders); +} + +static inline void ss_del(struct msg_sender *mss) +{ + if (mss->list.next) + list_del(&mss->list); +} + +static void ss_wakeup(struct msg_queue *msq, + struct wake_q_head *wake_q, bool kill) +{ + struct msg_sender *mss, *t; + struct task_struct *stop_tsk = NULL; + struct list_head *h = &msq->q_senders; + + list_for_each_entry_safe(mss, t, h, list) { + if (kill) + mss->list.next = NULL; + + /* + * Stop at the first task we don't wakeup, + * we've already iterated the original + * sender queue. + */ + else if (stop_tsk == mss->tsk) + break; + /* + * We are not in an EIDRM scenario here, therefore + * verify that we really need to wakeup the task. + * To maintain current semantics and wakeup order, + * move the sender to the tail on behalf of the + * blocked task. + */ + else if (!msg_fits_inqueue(msq, mss->msgsz)) { + if (!stop_tsk) + stop_tsk = mss->tsk; + + list_move_tail(&mss->list, &msq->q_senders); + continue; + } + + wake_q_add(wake_q, mss->tsk); + } +} + +static void expunge_all(struct msg_queue *msq, int res, + struct wake_q_head *wake_q) +{ + struct msg_receiver *msr, *t; + + list_for_each_entry_safe(msr, t, &msq->q_receivers, r_list) { + struct task_struct *r_tsk; + + r_tsk = get_task_struct(msr->r_tsk); + + /* see MSG_BARRIER for purpose/pairing */ + smp_store_release(&msr->r_msg, ERR_PTR(res)); + wake_q_add_safe(wake_q, r_tsk); + } +} + +/* + * freeque() wakes up waiters on the sender and receiver waiting queue, + * removes the message queue from message queue ID IDR, and cleans up all the + * messages associated with this queue. + * + * msg_ids.rwsem (writer) and the spinlock for this message queue are held + * before freeque() is called. msg_ids.rwsem remains locked on exit. + */ +static void freeque(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp) + __releases(RCU) + __releases(&msq->q_perm) +{ + struct msg_msg *msg, *t; + struct msg_queue *msq = container_of(ipcp, struct msg_queue, q_perm); + DEFINE_WAKE_Q(wake_q); + + expunge_all(msq, -EIDRM, &wake_q); + ss_wakeup(msq, &wake_q, true); + msg_rmid(ns, msq); + ipc_unlock_object(&msq->q_perm); + wake_up_q(&wake_q); + rcu_read_unlock(); + + list_for_each_entry_safe(msg, t, &msq->q_messages, m_list) { + percpu_counter_sub_local(&ns->percpu_msg_hdrs, 1); + free_msg(msg); + } + percpu_counter_sub_local(&ns->percpu_msg_bytes, msq->q_cbytes); + ipc_update_pid(&msq->q_lspid, NULL); + ipc_update_pid(&msq->q_lrpid, NULL); + ipc_rcu_putref(&msq->q_perm, msg_rcu_free); +} + +long ksys_msgget(key_t key, int msgflg) +{ + struct ipc_namespace *ns; + static const struct ipc_ops msg_ops = { + .getnew = newque, + .associate = security_msg_queue_associate, + }; + struct ipc_params msg_params; + + ns = current->nsproxy->ipc_ns; + + msg_params.key = key; + msg_params.flg = msgflg; + + return ipcget(ns, &msg_ids(ns), &msg_ops, &msg_params); +} + +SYSCALL_DEFINE2(msgget, key_t, key, int, msgflg) +{ + return ksys_msgget(key, msgflg); +} + +static inline unsigned long +copy_msqid_to_user(void __user *buf, struct msqid64_ds *in, int version) +{ + switch (version) { + case IPC_64: + return copy_to_user(buf, in, sizeof(*in)); + case IPC_OLD: + { + struct msqid_ds out; + + memset(&out, 0, sizeof(out)); + + ipc64_perm_to_ipc_perm(&in->msg_perm, &out.msg_perm); + + out.msg_stime = in->msg_stime; + out.msg_rtime = in->msg_rtime; + out.msg_ctime = in->msg_ctime; + + if (in->msg_cbytes > USHRT_MAX) + out.msg_cbytes = USHRT_MAX; + else + out.msg_cbytes = in->msg_cbytes; + out.msg_lcbytes = in->msg_cbytes; + + if (in->msg_qnum > USHRT_MAX) + out.msg_qnum = USHRT_MAX; + else + out.msg_qnum = in->msg_qnum; + + if (in->msg_qbytes > USHRT_MAX) + out.msg_qbytes = USHRT_MAX; + else + out.msg_qbytes = in->msg_qbytes; + out.msg_lqbytes = in->msg_qbytes; + + out.msg_lspid = in->msg_lspid; + out.msg_lrpid = in->msg_lrpid; + + return copy_to_user(buf, &out, sizeof(out)); + } + default: + return -EINVAL; + } +} + +static inline unsigned long +copy_msqid_from_user(struct msqid64_ds *out, void __user *buf, int version) +{ + switch (version) { + case IPC_64: + if (copy_from_user(out, buf, sizeof(*out))) + return -EFAULT; + return 0; + case IPC_OLD: + { + struct msqid_ds tbuf_old; + + if (copy_from_user(&tbuf_old, buf, sizeof(tbuf_old))) + return -EFAULT; + + out->msg_perm.uid = tbuf_old.msg_perm.uid; + out->msg_perm.gid = tbuf_old.msg_perm.gid; + out->msg_perm.mode = tbuf_old.msg_perm.mode; + + if (tbuf_old.msg_qbytes == 0) + out->msg_qbytes = tbuf_old.msg_lqbytes; + else + out->msg_qbytes = tbuf_old.msg_qbytes; + + return 0; + } + default: + return -EINVAL; + } +} + +/* + * This function handles some msgctl commands which require the rwsem + * to be held in write mode. + * NOTE: no locks must be held, the rwsem is taken inside this function. + */ +static int msgctl_down(struct ipc_namespace *ns, int msqid, int cmd, + struct ipc64_perm *perm, int msg_qbytes) +{ + struct kern_ipc_perm *ipcp; + struct msg_queue *msq; + int err; + + down_write(&msg_ids(ns).rwsem); + rcu_read_lock(); + + ipcp = ipcctl_obtain_check(ns, &msg_ids(ns), msqid, cmd, + perm, msg_qbytes); + if (IS_ERR(ipcp)) { + err = PTR_ERR(ipcp); + goto out_unlock1; + } + + msq = container_of(ipcp, struct msg_queue, q_perm); + + err = security_msg_queue_msgctl(&msq->q_perm, cmd); + if (err) + goto out_unlock1; + + switch (cmd) { + case IPC_RMID: + ipc_lock_object(&msq->q_perm); + /* freeque unlocks the ipc object and rcu */ + freeque(ns, ipcp); + goto out_up; + case IPC_SET: + { + DEFINE_WAKE_Q(wake_q); + + if (msg_qbytes > ns->msg_ctlmnb && + !capable(CAP_SYS_RESOURCE)) { + err = -EPERM; + goto out_unlock1; + } + + ipc_lock_object(&msq->q_perm); + err = ipc_update_perm(perm, ipcp); + if (err) + goto out_unlock0; + + msq->q_qbytes = msg_qbytes; + + msq->q_ctime = ktime_get_real_seconds(); + /* + * Sleeping receivers might be excluded by + * stricter permissions. + */ + expunge_all(msq, -EAGAIN, &wake_q); + /* + * Sleeping senders might be able to send + * due to a larger queue size. + */ + ss_wakeup(msq, &wake_q, false); + ipc_unlock_object(&msq->q_perm); + wake_up_q(&wake_q); + + goto out_unlock1; + } + default: + err = -EINVAL; + goto out_unlock1; + } + +out_unlock0: + ipc_unlock_object(&msq->q_perm); +out_unlock1: + rcu_read_unlock(); +out_up: + up_write(&msg_ids(ns).rwsem); + return err; +} + +static int msgctl_info(struct ipc_namespace *ns, int msqid, + int cmd, struct msginfo *msginfo) +{ + int err; + int max_idx; + + /* + * We must not return kernel stack data. + * due to padding, it's not enough + * to set all member fields. + */ + err = security_msg_queue_msgctl(NULL, cmd); + if (err) + return err; + + memset(msginfo, 0, sizeof(*msginfo)); + msginfo->msgmni = ns->msg_ctlmni; + msginfo->msgmax = ns->msg_ctlmax; + msginfo->msgmnb = ns->msg_ctlmnb; + msginfo->msgssz = MSGSSZ; + msginfo->msgseg = MSGSEG; + down_read(&msg_ids(ns).rwsem); + if (cmd == MSG_INFO) + msginfo->msgpool = msg_ids(ns).in_use; + max_idx = ipc_get_maxidx(&msg_ids(ns)); + up_read(&msg_ids(ns).rwsem); + if (cmd == MSG_INFO) { + msginfo->msgmap = min_t(int, + percpu_counter_sum(&ns->percpu_msg_hdrs), + INT_MAX); + msginfo->msgtql = min_t(int, + percpu_counter_sum(&ns->percpu_msg_bytes), + INT_MAX); + } else { + msginfo->msgmap = MSGMAP; + msginfo->msgpool = MSGPOOL; + msginfo->msgtql = MSGTQL; + } + return (max_idx < 0) ? 0 : max_idx; +} + +static int msgctl_stat(struct ipc_namespace *ns, int msqid, + int cmd, struct msqid64_ds *p) +{ + struct msg_queue *msq; + int err; + + memset(p, 0, sizeof(*p)); + + rcu_read_lock(); + if (cmd == MSG_STAT || cmd == MSG_STAT_ANY) { + msq = msq_obtain_object(ns, msqid); + if (IS_ERR(msq)) { + err = PTR_ERR(msq); + goto out_unlock; + } + } else { /* IPC_STAT */ + msq = msq_obtain_object_check(ns, msqid); + if (IS_ERR(msq)) { + err = PTR_ERR(msq); + goto out_unlock; + } + } + + /* see comment for SHM_STAT_ANY */ + if (cmd == MSG_STAT_ANY) + audit_ipc_obj(&msq->q_perm); + else { + err = -EACCES; + if (ipcperms(ns, &msq->q_perm, S_IRUGO)) + goto out_unlock; + } + + err = security_msg_queue_msgctl(&msq->q_perm, cmd); + if (err) + goto out_unlock; + + ipc_lock_object(&msq->q_perm); + + if (!ipc_valid_object(&msq->q_perm)) { + ipc_unlock_object(&msq->q_perm); + err = -EIDRM; + goto out_unlock; + } + + kernel_to_ipc64_perm(&msq->q_perm, &p->msg_perm); + p->msg_stime = msq->q_stime; + p->msg_rtime = msq->q_rtime; + p->msg_ctime = msq->q_ctime; +#ifndef CONFIG_64BIT + p->msg_stime_high = msq->q_stime >> 32; + p->msg_rtime_high = msq->q_rtime >> 32; + p->msg_ctime_high = msq->q_ctime >> 32; +#endif + p->msg_cbytes = msq->q_cbytes; + p->msg_qnum = msq->q_qnum; + p->msg_qbytes = msq->q_qbytes; + p->msg_lspid = pid_vnr(msq->q_lspid); + p->msg_lrpid = pid_vnr(msq->q_lrpid); + + if (cmd == IPC_STAT) { + /* + * As defined in SUS: + * Return 0 on success + */ + err = 0; + } else { + /* + * MSG_STAT and MSG_STAT_ANY (both Linux specific) + * Return the full id, including the sequence number + */ + err = msq->q_perm.id; + } + + ipc_unlock_object(&msq->q_perm); +out_unlock: + rcu_read_unlock(); + return err; +} + +static long ksys_msgctl(int msqid, int cmd, struct msqid_ds __user *buf, int version) +{ + struct ipc_namespace *ns; + struct msqid64_ds msqid64; + int err; + + if (msqid < 0 || cmd < 0) + return -EINVAL; + + ns = current->nsproxy->ipc_ns; + + switch (cmd) { + case IPC_INFO: + case MSG_INFO: { + struct msginfo msginfo; + err = msgctl_info(ns, msqid, cmd, &msginfo); + if (err < 0) + return err; + if (copy_to_user(buf, &msginfo, sizeof(struct msginfo))) + err = -EFAULT; + return err; + } + case MSG_STAT: /* msqid is an index rather than a msg queue id */ + case MSG_STAT_ANY: + case IPC_STAT: + err = msgctl_stat(ns, msqid, cmd, &msqid64); + if (err < 0) + return err; + if (copy_msqid_to_user(buf, &msqid64, version)) + err = -EFAULT; + return err; + case IPC_SET: + if (copy_msqid_from_user(&msqid64, buf, version)) + return -EFAULT; + return msgctl_down(ns, msqid, cmd, &msqid64.msg_perm, + msqid64.msg_qbytes); + case IPC_RMID: + return msgctl_down(ns, msqid, cmd, NULL, 0); + default: + return -EINVAL; + } +} + +SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf) +{ + return ksys_msgctl(msqid, cmd, buf, IPC_64); +} + +#ifdef CONFIG_ARCH_WANT_IPC_PARSE_VERSION +long ksys_old_msgctl(int msqid, int cmd, struct msqid_ds __user *buf) +{ + int version = ipc_parse_version(&cmd); + + return ksys_msgctl(msqid, cmd, buf, version); +} + +SYSCALL_DEFINE3(old_msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf) +{ + return ksys_old_msgctl(msqid, cmd, buf); +} +#endif + +#ifdef CONFIG_COMPAT + +struct compat_msqid_ds { + struct compat_ipc_perm msg_perm; + compat_uptr_t msg_first; + compat_uptr_t msg_last; + old_time32_t msg_stime; + old_time32_t msg_rtime; + old_time32_t msg_ctime; + compat_ulong_t msg_lcbytes; + compat_ulong_t msg_lqbytes; + unsigned short msg_cbytes; + unsigned short msg_qnum; + unsigned short msg_qbytes; + compat_ipc_pid_t msg_lspid; + compat_ipc_pid_t msg_lrpid; +}; + +static int copy_compat_msqid_from_user(struct msqid64_ds *out, void __user *buf, + int version) +{ + memset(out, 0, sizeof(*out)); + if (version == IPC_64) { + struct compat_msqid64_ds __user *p = buf; + if (get_compat_ipc64_perm(&out->msg_perm, &p->msg_perm)) + return -EFAULT; + if (get_user(out->msg_qbytes, &p->msg_qbytes)) + return -EFAULT; + } else { + struct compat_msqid_ds __user *p = buf; + if (get_compat_ipc_perm(&out->msg_perm, &p->msg_perm)) + return -EFAULT; + if (get_user(out->msg_qbytes, &p->msg_qbytes)) + return -EFAULT; + } + return 0; +} + +static int copy_compat_msqid_to_user(void __user *buf, struct msqid64_ds *in, + int version) +{ + if (version == IPC_64) { + struct compat_msqid64_ds v; + memset(&v, 0, sizeof(v)); + to_compat_ipc64_perm(&v.msg_perm, &in->msg_perm); + v.msg_stime = lower_32_bits(in->msg_stime); + v.msg_stime_high = upper_32_bits(in->msg_stime); + v.msg_rtime = lower_32_bits(in->msg_rtime); + v.msg_rtime_high = upper_32_bits(in->msg_rtime); + v.msg_ctime = lower_32_bits(in->msg_ctime); + v.msg_ctime_high = upper_32_bits(in->msg_ctime); + v.msg_cbytes = in->msg_cbytes; + v.msg_qnum = in->msg_qnum; + v.msg_qbytes = in->msg_qbytes; + v.msg_lspid = in->msg_lspid; + v.msg_lrpid = in->msg_lrpid; + return copy_to_user(buf, &v, sizeof(v)); + } else { + struct compat_msqid_ds v; + memset(&v, 0, sizeof(v)); + to_compat_ipc_perm(&v.msg_perm, &in->msg_perm); + v.msg_stime = in->msg_stime; + v.msg_rtime = in->msg_rtime; + v.msg_ctime = in->msg_ctime; + v.msg_cbytes = in->msg_cbytes; + v.msg_qnum = in->msg_qnum; + v.msg_qbytes = in->msg_qbytes; + v.msg_lspid = in->msg_lspid; + v.msg_lrpid = in->msg_lrpid; + return copy_to_user(buf, &v, sizeof(v)); + } +} + +static long compat_ksys_msgctl(int msqid, int cmd, void __user *uptr, int version) +{ + struct ipc_namespace *ns; + int err; + struct msqid64_ds msqid64; + + ns = current->nsproxy->ipc_ns; + + if (msqid < 0 || cmd < 0) + return -EINVAL; + + switch (cmd & (~IPC_64)) { + case IPC_INFO: + case MSG_INFO: { + struct msginfo msginfo; + err = msgctl_info(ns, msqid, cmd, &msginfo); + if (err < 0) + return err; + if (copy_to_user(uptr, &msginfo, sizeof(struct msginfo))) + err = -EFAULT; + return err; + } + case IPC_STAT: + case MSG_STAT: + case MSG_STAT_ANY: + err = msgctl_stat(ns, msqid, cmd, &msqid64); + if (err < 0) + return err; + if (copy_compat_msqid_to_user(uptr, &msqid64, version)) + err = -EFAULT; + return err; + case IPC_SET: + if (copy_compat_msqid_from_user(&msqid64, uptr, version)) + return -EFAULT; + return msgctl_down(ns, msqid, cmd, &msqid64.msg_perm, msqid64.msg_qbytes); + case IPC_RMID: + return msgctl_down(ns, msqid, cmd, NULL, 0); + default: + return -EINVAL; + } +} + +COMPAT_SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, void __user *, uptr) +{ + return compat_ksys_msgctl(msqid, cmd, uptr, IPC_64); +} + +#ifdef CONFIG_ARCH_WANT_COMPAT_IPC_PARSE_VERSION +long compat_ksys_old_msgctl(int msqid, int cmd, void __user *uptr) +{ + int version = compat_ipc_parse_version(&cmd); + + return compat_ksys_msgctl(msqid, cmd, uptr, version); +} + +COMPAT_SYSCALL_DEFINE3(old_msgctl, int, msqid, int, cmd, void __user *, uptr) +{ + return compat_ksys_old_msgctl(msqid, cmd, uptr); +} +#endif +#endif + +static int testmsg(struct msg_msg *msg, long type, int mode) +{ + switch (mode) { + case SEARCH_ANY: + case SEARCH_NUMBER: + return 1; + case SEARCH_LESSEQUAL: + if (msg->m_type <= type) + return 1; + break; + case SEARCH_EQUAL: + if (msg->m_type == type) + return 1; + break; + case SEARCH_NOTEQUAL: + if (msg->m_type != type) + return 1; + break; + } + return 0; +} + +static inline int pipelined_send(struct msg_queue *msq, struct msg_msg *msg, + struct wake_q_head *wake_q) +{ + struct msg_receiver *msr, *t; + + list_for_each_entry_safe(msr, t, &msq->q_receivers, r_list) { + if (testmsg(msg, msr->r_msgtype, msr->r_mode) && + !security_msg_queue_msgrcv(&msq->q_perm, msg, msr->r_tsk, + msr->r_msgtype, msr->r_mode)) { + + list_del(&msr->r_list); + if (msr->r_maxsize < msg->m_ts) { + wake_q_add(wake_q, msr->r_tsk); + + /* See expunge_all regarding memory barrier */ + smp_store_release(&msr->r_msg, ERR_PTR(-E2BIG)); + } else { + ipc_update_pid(&msq->q_lrpid, task_pid(msr->r_tsk)); + msq->q_rtime = ktime_get_real_seconds(); + + wake_q_add(wake_q, msr->r_tsk); + + /* See expunge_all regarding memory barrier */ + smp_store_release(&msr->r_msg, msg); + return 1; + } + } + } + + return 0; +} + +static long do_msgsnd(int msqid, long mtype, void __user *mtext, + size_t msgsz, int msgflg) +{ + struct msg_queue *msq; + struct msg_msg *msg; + int err; + struct ipc_namespace *ns; + DEFINE_WAKE_Q(wake_q); + + ns = current->nsproxy->ipc_ns; + + if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0) + return -EINVAL; + if (mtype < 1) + return -EINVAL; + + msg = load_msg(mtext, msgsz); + if (IS_ERR(msg)) + return PTR_ERR(msg); + + msg->m_type = mtype; + msg->m_ts = msgsz; + + rcu_read_lock(); + msq = msq_obtain_object_check(ns, msqid); + if (IS_ERR(msq)) { + err = PTR_ERR(msq); + goto out_unlock1; + } + + ipc_lock_object(&msq->q_perm); + + for (;;) { + struct msg_sender s; + + err = -EACCES; + if (ipcperms(ns, &msq->q_perm, S_IWUGO)) + goto out_unlock0; + + /* raced with RMID? */ + if (!ipc_valid_object(&msq->q_perm)) { + err = -EIDRM; + goto out_unlock0; + } + + err = security_msg_queue_msgsnd(&msq->q_perm, msg, msgflg); + if (err) + goto out_unlock0; + + if (msg_fits_inqueue(msq, msgsz)) + break; + + /* queue full, wait: */ + if (msgflg & IPC_NOWAIT) { + err = -EAGAIN; + goto out_unlock0; + } + + /* enqueue the sender and prepare to block */ + ss_add(msq, &s, msgsz); + + if (!ipc_rcu_getref(&msq->q_perm)) { + err = -EIDRM; + goto out_unlock0; + } + + ipc_unlock_object(&msq->q_perm); + rcu_read_unlock(); + schedule(); + + rcu_read_lock(); + ipc_lock_object(&msq->q_perm); + + ipc_rcu_putref(&msq->q_perm, msg_rcu_free); + /* raced with RMID? */ + if (!ipc_valid_object(&msq->q_perm)) { + err = -EIDRM; + goto out_unlock0; + } + ss_del(&s); + + if (signal_pending(current)) { + err = -ERESTARTNOHAND; + goto out_unlock0; + } + + } + + ipc_update_pid(&msq->q_lspid, task_tgid(current)); + msq->q_stime = ktime_get_real_seconds(); + + if (!pipelined_send(msq, msg, &wake_q)) { + /* no one is waiting for this message, enqueue it */ + list_add_tail(&msg->m_list, &msq->q_messages); + msq->q_cbytes += msgsz; + msq->q_qnum++; + percpu_counter_add_local(&ns->percpu_msg_bytes, msgsz); + percpu_counter_add_local(&ns->percpu_msg_hdrs, 1); + } + + err = 0; + msg = NULL; + +out_unlock0: + ipc_unlock_object(&msq->q_perm); + wake_up_q(&wake_q); +out_unlock1: + rcu_read_unlock(); + if (msg != NULL) + free_msg(msg); + return err; +} + +long ksys_msgsnd(int msqid, struct msgbuf __user *msgp, size_t msgsz, + int msgflg) +{ + long mtype; + + if (get_user(mtype, &msgp->mtype)) + return -EFAULT; + return do_msgsnd(msqid, mtype, msgp->mtext, msgsz, msgflg); +} + +SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz, + int, msgflg) +{ + return ksys_msgsnd(msqid, msgp, msgsz, msgflg); +} + +#ifdef CONFIG_COMPAT + +struct compat_msgbuf { + compat_long_t mtype; + char mtext[]; +}; + +long compat_ksys_msgsnd(int msqid, compat_uptr_t msgp, + compat_ssize_t msgsz, int msgflg) +{ + struct compat_msgbuf __user *up = compat_ptr(msgp); + compat_long_t mtype; + + if (get_user(mtype, &up->mtype)) + return -EFAULT; + return do_msgsnd(msqid, mtype, up->mtext, (ssize_t)msgsz, msgflg); +} + +COMPAT_SYSCALL_DEFINE4(msgsnd, int, msqid, compat_uptr_t, msgp, + compat_ssize_t, msgsz, int, msgflg) +{ + return compat_ksys_msgsnd(msqid, msgp, msgsz, msgflg); +} +#endif + +static inline int convert_mode(long *msgtyp, int msgflg) +{ + if (msgflg & MSG_COPY) + return SEARCH_NUMBER; + /* + * find message of correct type. + * msgtyp = 0 => get first. + * msgtyp > 0 => get first message of matching type. + * msgtyp < 0 => get message with least type must be < abs(msgtype). + */ + if (*msgtyp == 0) + return SEARCH_ANY; + if (*msgtyp < 0) { + if (*msgtyp == LONG_MIN) /* -LONG_MIN is undefined */ + *msgtyp = LONG_MAX; + else + *msgtyp = -*msgtyp; + return SEARCH_LESSEQUAL; + } + if (msgflg & MSG_EXCEPT) + return SEARCH_NOTEQUAL; + return SEARCH_EQUAL; +} + +static long do_msg_fill(void __user *dest, struct msg_msg *msg, size_t bufsz) +{ + struct msgbuf __user *msgp = dest; + size_t msgsz; + + if (put_user(msg->m_type, &msgp->mtype)) + return -EFAULT; + + msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz; + if (store_msg(msgp->mtext, msg, msgsz)) + return -EFAULT; + return msgsz; +} + +#ifdef CONFIG_CHECKPOINT_RESTORE +/* + * This function creates new kernel message structure, large enough to store + * bufsz message bytes. + */ +static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz) +{ + struct msg_msg *copy; + + /* + * Create dummy message to copy real message to. + */ + copy = load_msg(buf, bufsz); + if (!IS_ERR(copy)) + copy->m_ts = bufsz; + return copy; +} + +static inline void free_copy(struct msg_msg *copy) +{ + if (copy) + free_msg(copy); +} +#else +static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz) +{ + return ERR_PTR(-ENOSYS); +} + +static inline void free_copy(struct msg_msg *copy) +{ +} +#endif + +static struct msg_msg *find_msg(struct msg_queue *msq, long *msgtyp, int mode) +{ + struct msg_msg *msg, *found = NULL; + long count = 0; + + list_for_each_entry(msg, &msq->q_messages, m_list) { + if (testmsg(msg, *msgtyp, mode) && + !security_msg_queue_msgrcv(&msq->q_perm, msg, current, + *msgtyp, mode)) { + if (mode == SEARCH_LESSEQUAL && msg->m_type != 1) { + *msgtyp = msg->m_type - 1; + found = msg; + } else if (mode == SEARCH_NUMBER) { + if (*msgtyp == count) + return msg; + } else + return msg; + count++; + } + } + + return found ?: ERR_PTR(-EAGAIN); +} + +static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg, + long (*msg_handler)(void __user *, struct msg_msg *, size_t)) +{ + int mode; + struct msg_queue *msq; + struct ipc_namespace *ns; + struct msg_msg *msg, *copy = NULL; + DEFINE_WAKE_Q(wake_q); + + ns = current->nsproxy->ipc_ns; + + if (msqid < 0 || (long) bufsz < 0) + return -EINVAL; + + if (msgflg & MSG_COPY) { + if ((msgflg & MSG_EXCEPT) || !(msgflg & IPC_NOWAIT)) + return -EINVAL; + copy = prepare_copy(buf, min_t(size_t, bufsz, ns->msg_ctlmax)); + if (IS_ERR(copy)) + return PTR_ERR(copy); + } + mode = convert_mode(&msgtyp, msgflg); + + rcu_read_lock(); + msq = msq_obtain_object_check(ns, msqid); + if (IS_ERR(msq)) { + rcu_read_unlock(); + free_copy(copy); + return PTR_ERR(msq); + } + + for (;;) { + struct msg_receiver msr_d; + + msg = ERR_PTR(-EACCES); + if (ipcperms(ns, &msq->q_perm, S_IRUGO)) + goto out_unlock1; + + ipc_lock_object(&msq->q_perm); + + /* raced with RMID? */ + if (!ipc_valid_object(&msq->q_perm)) { + msg = ERR_PTR(-EIDRM); + goto out_unlock0; + } + + msg = find_msg(msq, &msgtyp, mode); + if (!IS_ERR(msg)) { + /* + * Found a suitable message. + * Unlink it from the queue. + */ + if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) { + msg = ERR_PTR(-E2BIG); + goto out_unlock0; + } + /* + * If we are copying, then do not unlink message and do + * not update queue parameters. + */ + if (msgflg & MSG_COPY) { + msg = copy_msg(msg, copy); + goto out_unlock0; + } + + list_del(&msg->m_list); + msq->q_qnum--; + msq->q_rtime = ktime_get_real_seconds(); + ipc_update_pid(&msq->q_lrpid, task_tgid(current)); + msq->q_cbytes -= msg->m_ts; + percpu_counter_sub_local(&ns->percpu_msg_bytes, msg->m_ts); + percpu_counter_sub_local(&ns->percpu_msg_hdrs, 1); + ss_wakeup(msq, &wake_q, false); + + goto out_unlock0; + } + + /* No message waiting. Wait for a message */ + if (msgflg & IPC_NOWAIT) { + msg = ERR_PTR(-ENOMSG); + goto out_unlock0; + } + + list_add_tail(&msr_d.r_list, &msq->q_receivers); + msr_d.r_tsk = current; + msr_d.r_msgtype = msgtyp; + msr_d.r_mode = mode; + if (msgflg & MSG_NOERROR) + msr_d.r_maxsize = INT_MAX; + else + msr_d.r_maxsize = bufsz; + + /* memory barrier not require due to ipc_lock_object() */ + WRITE_ONCE(msr_d.r_msg, ERR_PTR(-EAGAIN)); + + /* memory barrier not required, we own ipc_lock_object() */ + __set_current_state(TASK_INTERRUPTIBLE); + + ipc_unlock_object(&msq->q_perm); + rcu_read_unlock(); + schedule(); + + /* + * Lockless receive, part 1: + * We don't hold a reference to the queue and getting a + * reference would defeat the idea of a lockless operation, + * thus the code relies on rcu to guarantee the existence of + * msq: + * Prior to destruction, expunge_all(-EIRDM) changes r_msg. + * Thus if r_msg is -EAGAIN, then the queue not yet destroyed. + */ + rcu_read_lock(); + + /* + * Lockless receive, part 2: + * The work in pipelined_send() and expunge_all(): + * - Set pointer to message + * - Queue the receiver task for later wakeup + * - Wake up the process after the lock is dropped. + * + * Should the process wake up before this wakeup (due to a + * signal) it will either see the message and continue ... + */ + msg = READ_ONCE(msr_d.r_msg); + if (msg != ERR_PTR(-EAGAIN)) { + /* see MSG_BARRIER for purpose/pairing */ + smp_acquire__after_ctrl_dep(); + + goto out_unlock1; + } + + /* + * ... or see -EAGAIN, acquire the lock to check the message + * again. + */ + ipc_lock_object(&msq->q_perm); + + msg = READ_ONCE(msr_d.r_msg); + if (msg != ERR_PTR(-EAGAIN)) + goto out_unlock0; + + list_del(&msr_d.r_list); + if (signal_pending(current)) { + msg = ERR_PTR(-ERESTARTNOHAND); + goto out_unlock0; + } + + ipc_unlock_object(&msq->q_perm); + } + +out_unlock0: + ipc_unlock_object(&msq->q_perm); + wake_up_q(&wake_q); +out_unlock1: + rcu_read_unlock(); + if (IS_ERR(msg)) { + free_copy(copy); + return PTR_ERR(msg); + } + + bufsz = msg_handler(buf, msg, bufsz); + free_msg(msg); + + return bufsz; +} + +long ksys_msgrcv(int msqid, struct msgbuf __user *msgp, size_t msgsz, + long msgtyp, int msgflg) +{ + return do_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill); +} + +SYSCALL_DEFINE5(msgrcv, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz, + long, msgtyp, int, msgflg) +{ + return ksys_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg); +} + +#ifdef CONFIG_COMPAT +static long compat_do_msg_fill(void __user *dest, struct msg_msg *msg, size_t bufsz) +{ + struct compat_msgbuf __user *msgp = dest; + size_t msgsz; + + if (put_user(msg->m_type, &msgp->mtype)) + return -EFAULT; + + msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz; + if (store_msg(msgp->mtext, msg, msgsz)) + return -EFAULT; + return msgsz; +} + +long compat_ksys_msgrcv(int msqid, compat_uptr_t msgp, compat_ssize_t msgsz, + compat_long_t msgtyp, int msgflg) +{ + return do_msgrcv(msqid, compat_ptr(msgp), (ssize_t)msgsz, (long)msgtyp, + msgflg, compat_do_msg_fill); +} + +COMPAT_SYSCALL_DEFINE5(msgrcv, int, msqid, compat_uptr_t, msgp, + compat_ssize_t, msgsz, compat_long_t, msgtyp, + int, msgflg) +{ + return compat_ksys_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg); +} +#endif + +int msg_init_ns(struct ipc_namespace *ns) +{ + int ret; + + ns->msg_ctlmax = MSGMAX; + ns->msg_ctlmnb = MSGMNB; + ns->msg_ctlmni = MSGMNI; + + ret = percpu_counter_init(&ns->percpu_msg_bytes, 0, GFP_KERNEL); + if (ret) + goto fail_msg_bytes; + ret = percpu_counter_init(&ns->percpu_msg_hdrs, 0, GFP_KERNEL); + if (ret) + goto fail_msg_hdrs; + ipc_init_ids(&ns->ids[IPC_MSG_IDS]); + return 0; + +fail_msg_hdrs: + percpu_counter_destroy(&ns->percpu_msg_bytes); +fail_msg_bytes: + return ret; +} + +#ifdef CONFIG_IPC_NS +void msg_exit_ns(struct ipc_namespace *ns) +{ + free_ipcs(ns, &msg_ids(ns), freeque); + idr_destroy(&ns->ids[IPC_MSG_IDS].ipcs_idr); + rhashtable_destroy(&ns->ids[IPC_MSG_IDS].key_ht); + percpu_counter_destroy(&ns->percpu_msg_bytes); + percpu_counter_destroy(&ns->percpu_msg_hdrs); +} +#endif + +#ifdef CONFIG_PROC_FS +static int sysvipc_msg_proc_show(struct seq_file *s, void *it) +{ + struct pid_namespace *pid_ns = ipc_seq_pid_ns(s); + struct user_namespace *user_ns = seq_user_ns(s); + struct kern_ipc_perm *ipcp = it; + struct msg_queue *msq = container_of(ipcp, struct msg_queue, q_perm); + + seq_printf(s, + "%10d %10d %4o %10lu %10lu %5u %5u %5u %5u %5u %5u %10llu %10llu %10llu\n", + msq->q_perm.key, + msq->q_perm.id, + msq->q_perm.mode, + msq->q_cbytes, + msq->q_qnum, + pid_nr_ns(msq->q_lspid, pid_ns), + pid_nr_ns(msq->q_lrpid, pid_ns), + from_kuid_munged(user_ns, msq->q_perm.uid), + from_kgid_munged(user_ns, msq->q_perm.gid), + from_kuid_munged(user_ns, msq->q_perm.cuid), + from_kgid_munged(user_ns, msq->q_perm.cgid), + msq->q_stime, + msq->q_rtime, + msq->q_ctime); + + return 0; +} +#endif + +void __init msg_init(void) +{ + msg_init_ns(&init_ipc_ns); + + ipc_init_proc_interface("sysvipc/msg", + " key msqid perms cbytes qnum lspid lrpid uid gid cuid cgid stime rtime ctime\n", + IPC_MSG_IDS, sysvipc_msg_proc_show); +} diff --git a/ipc/msgutil.c b/ipc/msgutil.c new file mode 100644 index 000000000..e28f0cecb --- /dev/null +++ b/ipc/msgutil.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * linux/ipc/msgutil.c + * Copyright (C) 1999, 2004 Manfred Spraul + */ + +#include <linux/spinlock.h> +#include <linux/init.h> +#include <linux/security.h> +#include <linux/slab.h> +#include <linux/ipc.h> +#include <linux/msg.h> +#include <linux/ipc_namespace.h> +#include <linux/utsname.h> +#include <linux/proc_ns.h> +#include <linux/uaccess.h> +#include <linux/sched.h> +#include <linux/nstree.h> + +#include "util.h" + +DEFINE_SPINLOCK(mq_lock); + +/* + * The next 2 defines are here bc this is the only file + * compiled when either CONFIG_SYSVIPC and CONFIG_POSIX_MQUEUE + * and not CONFIG_IPC_NS. + */ +struct ipc_namespace init_ipc_ns = { + .ns = NS_COMMON_INIT(init_ipc_ns), + .user_ns = &init_user_ns, +}; + +struct msg_msgseg { + struct msg_msgseg *next; + /* the next part of the message follows immediately */ +}; + +#define DATALEN_MSG ((size_t)PAGE_SIZE-sizeof(struct msg_msg)) +#define DATALEN_SEG ((size_t)PAGE_SIZE-sizeof(struct msg_msgseg)) + +static kmem_buckets *msg_buckets __ro_after_init; + +static int __init init_msg_buckets(void) +{ + msg_buckets = kmem_buckets_create("msg_msg", SLAB_ACCOUNT, + sizeof(struct msg_msg), + DATALEN_MSG, NULL); + + return 0; +} +subsys_initcall(init_msg_buckets); + +static struct msg_msg *alloc_msg(size_t len) +{ + struct msg_msg *msg; + struct msg_msgseg **pseg; + size_t alen; + + alen = min(len, DATALEN_MSG); + msg = kmem_buckets_alloc(msg_buckets, sizeof(*msg) + alen, GFP_KERNEL); + if (msg == NULL) + return NULL; + + msg->next = NULL; + msg->security = NULL; + + len -= alen; + pseg = &msg->next; + while (len > 0) { + struct msg_msgseg *seg; + + cond_resched(); + + alen = min(len, DATALEN_SEG); + seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT); + if (seg == NULL) + goto out_err; + *pseg = seg; + seg->next = NULL; + pseg = &seg->next; + len -= alen; + } + + return msg; + +out_err: + free_msg(msg); + return NULL; +} + +struct msg_msg *load_msg(const void __user *src, size_t len) +{ + struct msg_msg *msg; + struct msg_msgseg *seg; + int err = -EFAULT; + size_t alen; + + msg = alloc_msg(len); + if (msg == NULL) + return ERR_PTR(-ENOMEM); + + alen = min(len, DATALEN_MSG); + if (copy_from_user(msg + 1, src, alen)) + goto out_err; + + for (seg = msg->next; seg != NULL; seg = seg->next) { + len -= alen; + src = (char __user *)src + alen; + alen = min(len, DATALEN_SEG); + if (copy_from_user(seg + 1, src, alen)) + goto out_err; + } + + err = security_msg_msg_alloc(msg); + if (err) + goto out_err; + + return msg; + +out_err: + free_msg(msg); + return ERR_PTR(err); +} +#ifdef CONFIG_CHECKPOINT_RESTORE +struct msg_msg *copy_msg(struct msg_msg *src, struct msg_msg *dst) +{ + struct msg_msgseg *dst_pseg, *src_pseg; + size_t len = src->m_ts; + size_t alen; + + if (src->m_ts > dst->m_ts) + return ERR_PTR(-EINVAL); + + alen = min(len, DATALEN_MSG); + memcpy(dst + 1, src + 1, alen); + + for (dst_pseg = dst->next, src_pseg = src->next; + src_pseg != NULL; + dst_pseg = dst_pseg->next, src_pseg = src_pseg->next) { + + len -= alen; + alen = min(len, DATALEN_SEG); + memcpy(dst_pseg + 1, src_pseg + 1, alen); + } + + dst->m_type = src->m_type; + dst->m_ts = src->m_ts; + + return dst; +} +#else +struct msg_msg *copy_msg(struct msg_msg *src, struct msg_msg *dst) +{ + return ERR_PTR(-ENOSYS); +} +#endif +int store_msg(void __user *dest, struct msg_msg *msg, size_t len) +{ + size_t alen; + struct msg_msgseg *seg; + + alen = min(len, DATALEN_MSG); + if (copy_to_user(dest, msg + 1, alen)) + return -1; + + for (seg = msg->next; seg != NULL; seg = seg->next) { + len -= alen; + dest = (char __user *)dest + alen; + alen = min(len, DATALEN_SEG); + if (copy_to_user(dest, seg + 1, alen)) + return -1; + } + return 0; +} + +void free_msg(struct msg_msg *msg) +{ + struct msg_msgseg *seg; + + security_msg_msg_free(msg); + + seg = msg->next; + kfree(msg); + while (seg != NULL) { + struct msg_msgseg *tmp = seg->next; + + cond_resched(); + kfree(seg); + seg = tmp; + } +} diff --git a/ipc/namespace.c b/ipc/namespace.c new file mode 100644 index 000000000..1e71353bd --- /dev/null +++ b/ipc/namespace.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/ipc/namespace.c + * Copyright (C) 2006 Pavel Emelyanov <xemul@openvz.org> OpenVZ, SWsoft Inc. + */ + +#include <linux/ipc.h> +#include <linux/msg.h> +#include <linux/ipc_namespace.h> +#include <linux/rcupdate.h> +#include <linux/nsproxy.h> +#include <linux/slab.h> +#include <linux/cred.h> +#include <linux/fs.h> +#include <linux/mount.h> +#include <linux/user_namespace.h> +#include <linux/proc_ns.h> +#include <linux/nstree.h> +#include <linux/sched/task.h> + +#include "util.h" + +/* + * The work queue is used to avoid the cost of synchronize_rcu in kern_unmount. + */ +static void free_ipc(struct work_struct *unused); +static DECLARE_WORK(free_ipc_work, free_ipc); + +static struct ucounts *inc_ipc_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_IPC_NAMESPACES); +} + +static void dec_ipc_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_IPC_NAMESPACES); +} + +static struct ipc_namespace *create_ipc_ns(struct user_namespace *user_ns, + struct ipc_namespace *old_ns) +{ + struct ipc_namespace *ns; + struct ucounts *ucounts; + int err; + + err = -ENOSPC; + again: + ucounts = inc_ipc_namespaces(user_ns); + if (!ucounts) { + /* + * IPC namespaces are freed asynchronously, by free_ipc_work. + * If frees were pending, flush_work will wait, and + * return true. Fail the allocation if no frees are pending. + */ + if (flush_work(&free_ipc_work)) + goto again; + goto fail; + } + + err = -ENOMEM; + ns = kzalloc_obj(struct ipc_namespace, GFP_KERNEL_ACCOUNT); + if (ns == NULL) + goto fail_dec; + + err = ns_common_init(ns); + if (err) + goto fail_free; + + ns_tree_gen_id(ns); + ns->user_ns = get_user_ns(user_ns); + ns->ucounts = ucounts; + + err = mq_init_ns(ns); + if (err) + goto fail_put; + + err = -ENOMEM; + if (!setup_mq_sysctls(ns)) + goto fail_mq_mount; + + if (!setup_ipc_sysctls(ns)) + goto fail_mq_sysctls; + + err = msg_init_ns(ns); + if (err) + goto fail_ipc; + + sem_init_ns(ns); + shm_init_ns(ns); + ns_tree_add_raw(ns); + + return ns; + +fail_ipc: + retire_ipc_sysctls(ns); +fail_mq_sysctls: + retire_mq_sysctls(ns); +fail_mq_mount: + mntput(ns->mq_mnt); +fail_put: + put_user_ns(ns->user_ns); + ns_common_free(ns); +fail_free: + kfree(ns); +fail_dec: + dec_ipc_namespaces(ucounts); +fail: + return ERR_PTR(err); +} + +struct ipc_namespace *copy_ipcs(u64 flags, + struct user_namespace *user_ns, struct ipc_namespace *ns) +{ + if (!(flags & CLONE_NEWIPC)) + return get_ipc_ns(ns); + return create_ipc_ns(user_ns, ns); +} + +/* + * free_ipcs - free all ipcs of one type + * @ns: the namespace to remove the ipcs from + * @ids: the table of ipcs to free + * @free: the function called to free each individual ipc + * + * Called for each kind of ipc when an ipc_namespace exits. + */ +void free_ipcs(struct ipc_namespace *ns, struct ipc_ids *ids, + void (*free)(struct ipc_namespace *, struct kern_ipc_perm *)) +{ + struct kern_ipc_perm *perm; + int next_id; + int total, in_use; + + down_write(&ids->rwsem); + + in_use = ids->in_use; + + for (total = 0, next_id = 0; total < in_use; next_id++) { + perm = idr_find(&ids->ipcs_idr, next_id); + if (perm == NULL) + continue; + rcu_read_lock(); + ipc_lock_object(perm); + free(ns, perm); + total++; + } + up_write(&ids->rwsem); +} + +static void free_ipc_ns(struct ipc_namespace *ns) +{ + /* + * Caller needs to wait for an RCU grace period to have passed + * after making the mount point inaccessible to new accesses. + */ + mntput(ns->mq_mnt); + sem_exit_ns(ns); + msg_exit_ns(ns); + shm_exit_ns(ns); + + retire_mq_sysctls(ns); + retire_ipc_sysctls(ns); + + dec_ipc_namespaces(ns->ucounts); + put_user_ns(ns->user_ns); + ns_common_free(ns); + kfree(ns); +} + +static LLIST_HEAD(free_ipc_list); +static void free_ipc(struct work_struct *unused) +{ + struct llist_node *node = llist_del_all(&free_ipc_list); + struct ipc_namespace *n, *t; + + llist_for_each_entry_safe(n, t, node, mnt_llist) + mnt_make_shortterm(n->mq_mnt); + + /* Wait for any last users to have gone away. */ + synchronize_rcu(); + + llist_for_each_entry_safe(n, t, node, mnt_llist) + free_ipc_ns(n); +} + +/* + * put_ipc_ns - drop a reference to an ipc namespace. + * @ns: the namespace to put + * + * If this is the last task in the namespace exiting, and + * it is dropping the refcount to 0, then it can race with + * a task in another ipc namespace but in a mounts namespace + * which has this ipcns's mqueuefs mounted, doing some action + * with one of the mqueuefs files. That can raise the refcount. + * So dropping the refcount, and raising the refcount when + * accessing it through the VFS, are protected with mq_lock. + * + * (Clearly, a task raising the refcount on its own ipc_ns + * needn't take mq_lock since it can't race with the last task + * in the ipcns exiting). + */ +void put_ipc_ns(struct ipc_namespace *ns) +{ + if (ns_ref_put_and_lock(ns, &mq_lock)) { + mq_clear_sbinfo(ns); + spin_unlock(&mq_lock); + + ns_tree_remove(ns); + if (llist_add(&ns->mnt_llist, &free_ipc_list)) + schedule_work(&free_ipc_work); + } +} + +static struct ns_common *ipcns_get(struct task_struct *task) +{ + struct ipc_namespace *ns = NULL; + struct nsproxy *nsproxy; + + task_lock(task); + nsproxy = task->nsproxy; + if (nsproxy) + ns = get_ipc_ns(nsproxy->ipc_ns); + task_unlock(task); + + return ns ? &ns->ns : NULL; +} + +static void ipcns_put(struct ns_common *ns) +{ + return put_ipc_ns(to_ipc_ns(ns)); +} + +static int ipcns_install(struct nsset *nsset, struct ns_common *new) +{ + struct nsproxy *nsproxy = nsset->nsproxy; + struct ipc_namespace *ns = to_ipc_ns(new); + if (!ns_capable(ns->user_ns, CAP_SYS_ADMIN) || + !ns_capable(nsset->cred->user_ns, CAP_SYS_ADMIN)) + return -EPERM; + + put_ipc_ns(nsproxy->ipc_ns); + nsproxy->ipc_ns = get_ipc_ns(ns); + return 0; +} + +static struct user_namespace *ipcns_owner(struct ns_common *ns) +{ + return to_ipc_ns(ns)->user_ns; +} + +const struct proc_ns_operations ipcns_operations = { + .name = "ipc", + .get = ipcns_get, + .put = ipcns_put, + .install = ipcns_install, + .owner = ipcns_owner, +}; diff --git a/ipc/sem.c b/ipc/sem.c new file mode 100644 index 000000000..5ec41de7e --- /dev/null +++ b/ipc/sem.c @@ -0,0 +1,2484 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/ipc/sem.c + * Copyright (C) 1992 Krishna Balasubramanian + * Copyright (C) 1995 Eric Schenk, Bruno Haible + * + * /proc/sysvipc/sem support (c) 1999 Dragos Acostachioaie <dragos@iname.com> + * + * SMP-threaded, sysctl's added + * (c) 1999 Manfred Spraul <manfred@colorfullife.com> + * Enforced range limit on SEM_UNDO + * (c) 2001 Red Hat Inc + * Lockless wakeup + * (c) 2003 Manfred Spraul <manfred@colorfullife.com> + * (c) 2016 Davidlohr Bueso <dave@stgolabs.net> + * Further wakeup optimizations, documentation + * (c) 2010 Manfred Spraul <manfred@colorfullife.com> + * + * support for audit of ipc object properties and permission changes + * Dustin Kirkland <dustin.kirkland@us.ibm.com> + * + * namespaces support + * OpenVZ, SWsoft Inc. + * Pavel Emelianov <xemul@openvz.org> + * + * Implementation notes: (May 2010) + * This file implements System V semaphores. + * + * User space visible behavior: + * - FIFO ordering for semop() operations (just FIFO, not starvation + * protection) + * - multiple semaphore operations that alter the same semaphore in + * one semop() are handled. + * - sem_ctime (time of last semctl()) is updated in the IPC_SET, SETVAL and + * SETALL calls. + * - two Linux specific semctl() commands: SEM_STAT, SEM_INFO. + * - undo adjustments at process exit are limited to 0..SEMVMX. + * - namespace are supported. + * - SEMMSL, SEMMNS, SEMOPM and SEMMNI can be configured at runtime by writing + * to /proc/sys/kernel/sem. + * - statistics about the usage are reported in /proc/sysvipc/sem. + * + * Internals: + * - scalability: + * - all global variables are read-mostly. + * - semop() calls and semctl(RMID) are synchronized by RCU. + * - most operations do write operations (actually: spin_lock calls) to + * the per-semaphore array structure. + * Thus: Perfect SMP scaling between independent semaphore arrays. + * If multiple semaphores in one array are used, then cache line + * trashing on the semaphore array spinlock will limit the scaling. + * - semncnt and semzcnt are calculated on demand in count_semcnt() + * - the task that performs a successful semop() scans the list of all + * sleeping tasks and completes any pending operations that can be fulfilled. + * Semaphores are actively given to waiting tasks (necessary for FIFO). + * (see update_queue()) + * - To improve the scalability, the actual wake-up calls are performed after + * dropping all locks. (see wake_up_sem_queue_prepare()) + * - All work is done by the waker, the woken up task does not have to do + * anything - not even acquiring a lock or dropping a refcount. + * - A woken up task may not even touch the semaphore array anymore, it may + * have been destroyed already by a semctl(RMID). + * - UNDO values are stored in an array (one per process and per + * semaphore array, lazily allocated). For backwards compatibility, multiple + * modes for the UNDO variables are supported (per process, per thread) + * (see copy_semundo, CLONE_SYSVSEM) + * - There are two lists of the pending operations: a per-array list + * and per-semaphore list (stored in the array). This allows to achieve FIFO + * ordering without always scanning all pending operations. + * The worst-case behavior is nevertheless O(N^2) for N wakeups. + */ + +#include <linux/compat.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/init.h> +#include <linux/proc_fs.h> +#include <linux/time.h> +#include <linux/security.h> +#include <linux/syscalls.h> +#include <linux/audit.h> +#include <linux/capability.h> +#include <linux/seq_file.h> +#include <linux/rwsem.h> +#include <linux/nsproxy.h> +#include <linux/ipc_namespace.h> +#include <linux/sched/wake_q.h> +#include <linux/nospec.h> +#include <linux/rhashtable.h> + +#include <linux/uaccess.h> +#include "util.h" + +/* One semaphore structure for each semaphore in the system. */ +struct sem { + int semval; /* current value */ + /* + * PID of the process that last modified the semaphore. For + * Linux, specifically these are: + * - semop + * - semctl, via SETVAL and SETALL. + * - at task exit when performing undo adjustments (see exit_sem). + */ + struct pid *sempid; + spinlock_t lock; /* spinlock for fine-grained semtimedop */ + struct list_head pending_alter; /* pending single-sop operations */ + /* that alter the semaphore */ + struct list_head pending_const; /* pending single-sop operations */ + /* that do not alter the semaphore*/ + time64_t sem_otime; /* candidate for sem_otime */ +} ____cacheline_aligned_in_smp; + +/* One sem_array data structure for each set of semaphores in the system. */ +struct sem_array { + struct kern_ipc_perm sem_perm; /* permissions .. see ipc.h */ + time64_t sem_ctime; /* create/last semctl() time */ + struct list_head pending_alter; /* pending operations */ + /* that alter the array */ + struct list_head pending_const; /* pending complex operations */ + /* that do not alter semvals */ + struct list_head list_id; /* undo requests on this array */ + int sem_nsems; /* no. of semaphores in array */ + int complex_count; /* pending complex operations */ + unsigned int use_global_lock;/* >0: global lock required */ + + struct sem sems[]; +} __randomize_layout; + +/* One queue for each sleeping process in the system. */ +struct sem_queue { + struct list_head list; /* queue of pending operations */ + struct task_struct *sleeper; /* this process */ + struct sem_undo *undo; /* undo structure */ + struct pid *pid; /* process id of requesting process */ + int status; /* completion status of operation */ + struct sembuf *sops; /* array of pending operations */ + struct sembuf *blocking; /* the operation that blocked */ + int nsops; /* number of operations */ + bool alter; /* does *sops alter the array? */ + bool dupsop; /* sops on more than one sem_num */ +}; + +/* Each task has a list of undo requests. They are executed automatically + * when the process exits. + */ +struct sem_undo { + struct list_head list_proc; /* per-process list: * + * all undos from one process + * rcu protected */ + struct rcu_head rcu; /* rcu struct for sem_undo */ + struct sem_undo_list *ulp; /* back ptr to sem_undo_list */ + struct list_head list_id; /* per semaphore array list: + * all undos for one array */ + int semid; /* semaphore set identifier */ + short semadj[]; /* array of adjustments */ + /* one per semaphore */ +}; + +/* sem_undo_list controls shared access to the list of sem_undo structures + * that may be shared among all a CLONE_SYSVSEM task group. + */ +struct sem_undo_list { + refcount_t refcnt; + spinlock_t lock; + struct list_head list_proc; +}; + + +#define sem_ids(ns) ((ns)->ids[IPC_SEM_IDS]) + +static int newary(struct ipc_namespace *, struct ipc_params *); +static void freeary(struct ipc_namespace *, struct kern_ipc_perm *); +#ifdef CONFIG_PROC_FS +static int sysvipc_sem_proc_show(struct seq_file *s, void *it); +#endif + +#define SEMMSL_FAST 256 /* 512 bytes on stack */ +#define SEMOPM_FAST 64 /* ~ 372 bytes on stack */ + +/* + * Switching from the mode suitable for simple ops + * to the mode for complex ops is costly. Therefore: + * use some hysteresis + */ +#define USE_GLOBAL_LOCK_HYSTERESIS 10 + +/* + * Locking: + * a) global sem_lock() for read/write + * sem_undo.id_next, + * sem_array.complex_count, + * sem_array.pending{_alter,_const}, + * sem_array.sem_undo + * + * b) global or semaphore sem_lock() for read/write: + * sem_array.sems[i].pending_{const,alter}: + * + * c) special: + * sem_undo_list.list_proc: + * * undo_list->lock for write + * * rcu for read + * use_global_lock: + * * global sem_lock() for write + * * either local or global sem_lock() for read. + * + * Memory ordering: + * Most ordering is enforced by using spin_lock() and spin_unlock(). + * + * Exceptions: + * 1) use_global_lock: (SEM_BARRIER_1) + * Setting it from non-zero to 0 is a RELEASE, this is ensured by + * using smp_store_release(): Immediately after setting it to 0, + * a simple op can start. + * Testing if it is non-zero is an ACQUIRE, this is ensured by using + * smp_load_acquire(). + * Setting it from 0 to non-zero must be ordered with regards to + * this smp_load_acquire(), this is guaranteed because the smp_load_acquire() + * is inside a spin_lock() and after a write from 0 to non-zero a + * spin_lock()+spin_unlock() is done. + * To prevent the compiler/cpu temporarily writing 0 to use_global_lock, + * READ_ONCE()/WRITE_ONCE() is used. + * + * 2) queue.status: (SEM_BARRIER_2) + * Initialization is done while holding sem_lock(), so no further barrier is + * required. + * Setting it to a result code is a RELEASE, this is ensured by both a + * smp_store_release() (for case a) and while holding sem_lock() + * (for case b). + * The ACQUIRE when reading the result code without holding sem_lock() is + * achieved by using READ_ONCE() + smp_acquire__after_ctrl_dep(). + * (case a above). + * Reading the result code while holding sem_lock() needs no further barriers, + * the locks inside sem_lock() enforce ordering (case b above) + * + * 3) current->state: + * current->state is set to TASK_INTERRUPTIBLE while holding sem_lock(). + * The wakeup is handled using the wake_q infrastructure. wake_q wakeups may + * happen immediately after calling wake_q_add. As wake_q_add_safe() is called + * when holding sem_lock(), no further barriers are required. + * + * See also ipc/mqueue.c for more details on the covered races. + */ + +#define sc_semmsl sem_ctls[0] +#define sc_semmns sem_ctls[1] +#define sc_semopm sem_ctls[2] +#define sc_semmni sem_ctls[3] + +void sem_init_ns(struct ipc_namespace *ns) +{ + ns->sc_semmsl = SEMMSL; + ns->sc_semmns = SEMMNS; + ns->sc_semopm = SEMOPM; + ns->sc_semmni = SEMMNI; + ns->used_sems = 0; + ipc_init_ids(&ns->ids[IPC_SEM_IDS]); +} + +#ifdef CONFIG_IPC_NS +void sem_exit_ns(struct ipc_namespace *ns) +{ + free_ipcs(ns, &sem_ids(ns), freeary); + idr_destroy(&ns->ids[IPC_SEM_IDS].ipcs_idr); + rhashtable_destroy(&ns->ids[IPC_SEM_IDS].key_ht); +} +#endif + +void __init sem_init(void) +{ + sem_init_ns(&init_ipc_ns); + ipc_init_proc_interface("sysvipc/sem", + " key semid perms nsems uid gid cuid cgid otime ctime\n", + IPC_SEM_IDS, sysvipc_sem_proc_show); +} + +/** + * unmerge_queues - unmerge queues, if possible. + * @sma: semaphore array + * + * The function unmerges the wait queues if complex_count is 0. + * It must be called prior to dropping the global semaphore array lock. + */ +static void unmerge_queues(struct sem_array *sma) +{ + struct sem_queue *q, *tq; + + /* complex operations still around? */ + if (sma->complex_count) + return; + /* + * We will switch back to simple mode. + * Move all pending operation back into the per-semaphore + * queues. + */ + list_for_each_entry_safe(q, tq, &sma->pending_alter, list) { + struct sem *curr; + curr = &sma->sems[q->sops[0].sem_num]; + + list_add_tail(&q->list, &curr->pending_alter); + } + INIT_LIST_HEAD(&sma->pending_alter); +} + +/** + * merge_queues - merge single semop queues into global queue + * @sma: semaphore array + * + * This function merges all per-semaphore queues into the global queue. + * It is necessary to achieve FIFO ordering for the pending single-sop + * operations when a multi-semop operation must sleep. + * Only the alter operations must be moved, the const operations can stay. + */ +static void merge_queues(struct sem_array *sma) +{ + int i; + for (i = 0; i < sma->sem_nsems; i++) { + struct sem *sem = &sma->sems[i]; + + list_splice_init(&sem->pending_alter, &sma->pending_alter); + } +} + +static void sem_rcu_free(struct rcu_head *head) +{ + struct kern_ipc_perm *p = container_of(head, struct kern_ipc_perm, rcu); + struct sem_array *sma = container_of(p, struct sem_array, sem_perm); + + security_sem_free(&sma->sem_perm); + kvfree(sma); +} + +/* + * Enter the mode suitable for non-simple operations: + * Caller must own sem_perm.lock. + */ +static void complexmode_enter(struct sem_array *sma) +{ + int i; + struct sem *sem; + + if (sma->use_global_lock > 0) { + /* + * We are already in global lock mode. + * Nothing to do, just reset the + * counter until we return to simple mode. + */ + WRITE_ONCE(sma->use_global_lock, USE_GLOBAL_LOCK_HYSTERESIS); + return; + } + WRITE_ONCE(sma->use_global_lock, USE_GLOBAL_LOCK_HYSTERESIS); + + for (i = 0; i < sma->sem_nsems; i++) { + sem = &sma->sems[i]; + spin_lock(&sem->lock); + spin_unlock(&sem->lock); + } +} + +/* + * Try to leave the mode that disallows simple operations: + * Caller must own sem_perm.lock. + */ +static void complexmode_tryleave(struct sem_array *sma) +{ + if (sma->complex_count) { + /* Complex ops are sleeping. + * We must stay in complex mode + */ + return; + } + if (sma->use_global_lock == 1) { + + /* See SEM_BARRIER_1 for purpose/pairing */ + smp_store_release(&sma->use_global_lock, 0); + } else { + WRITE_ONCE(sma->use_global_lock, + sma->use_global_lock-1); + } +} + +#define SEM_GLOBAL_LOCK (-1) +/* + * If the request contains only one semaphore operation, and there are + * no complex transactions pending, lock only the semaphore involved. + * Otherwise, lock the entire semaphore array, since we either have + * multiple semaphores in our own semops, or we need to look at + * semaphores from other pending complex operations. + */ +static inline int sem_lock(struct sem_array *sma, struct sembuf *sops, + int nsops) +{ + struct sem *sem; + int idx; + + if (nsops != 1) { + /* Complex operation - acquire a full lock */ + ipc_lock_object(&sma->sem_perm); + + /* Prevent parallel simple ops */ + complexmode_enter(sma); + return SEM_GLOBAL_LOCK; + } + + /* + * Only one semaphore affected - try to optimize locking. + * Optimized locking is possible if no complex operation + * is either enqueued or processed right now. + * + * Both facts are tracked by use_global_mode. + */ + idx = array_index_nospec(sops->sem_num, sma->sem_nsems); + sem = &sma->sems[idx]; + + /* + * Initial check for use_global_lock. Just an optimization, + * no locking, no memory barrier. + */ + if (!READ_ONCE(sma->use_global_lock)) { + /* + * It appears that no complex operation is around. + * Acquire the per-semaphore lock. + */ + spin_lock(&sem->lock); + + /* see SEM_BARRIER_1 for purpose/pairing */ + if (!smp_load_acquire(&sma->use_global_lock)) { + /* fast path successful! */ + return sops->sem_num; + } + spin_unlock(&sem->lock); + } + + /* slow path: acquire the full lock */ + ipc_lock_object(&sma->sem_perm); + + if (sma->use_global_lock == 0) { + /* + * The use_global_lock mode ended while we waited for + * sma->sem_perm.lock. Thus we must switch to locking + * with sem->lock. + * Unlike in the fast path, there is no need to recheck + * sma->use_global_lock after we have acquired sem->lock: + * We own sma->sem_perm.lock, thus use_global_lock cannot + * change. + */ + spin_lock(&sem->lock); + + ipc_unlock_object(&sma->sem_perm); + return sops->sem_num; + } else { + /* + * Not a false alarm, thus continue to use the global lock + * mode. No need for complexmode_enter(), this was done by + * the caller that has set use_global_mode to non-zero. + */ + return SEM_GLOBAL_LOCK; + } +} + +static inline void sem_unlock(struct sem_array *sma, int locknum) +{ + if (locknum == SEM_GLOBAL_LOCK) { + unmerge_queues(sma); + complexmode_tryleave(sma); + ipc_unlock_object(&sma->sem_perm); + } else { + struct sem *sem = &sma->sems[locknum]; + spin_unlock(&sem->lock); + } +} + +/* + * sem_lock_(check_) routines are called in the paths where the rwsem + * is not held. + * + * The caller holds the RCU read lock. + */ +static inline struct sem_array *sem_obtain_object(struct ipc_namespace *ns, int id) +{ + struct kern_ipc_perm *ipcp = ipc_obtain_object_idr(&sem_ids(ns), id); + + if (IS_ERR(ipcp)) + return ERR_CAST(ipcp); + + return container_of(ipcp, struct sem_array, sem_perm); +} + +static inline struct sem_array *sem_obtain_object_check(struct ipc_namespace *ns, + int id) +{ + struct kern_ipc_perm *ipcp = ipc_obtain_object_check(&sem_ids(ns), id); + + if (IS_ERR(ipcp)) + return ERR_CAST(ipcp); + + return container_of(ipcp, struct sem_array, sem_perm); +} + +static inline void sem_lock_and_putref(struct sem_array *sma) +{ + sem_lock(sma, NULL, -1); + ipc_rcu_putref(&sma->sem_perm, sem_rcu_free); +} + +static inline void sem_rmid(struct ipc_namespace *ns, struct sem_array *s) +{ + ipc_rmid(&sem_ids(ns), &s->sem_perm); +} + +static struct sem_array *sem_alloc(size_t nsems) +{ + struct sem_array *sma; + + if (nsems > (INT_MAX - sizeof(*sma)) / sizeof(sma->sems[0])) + return NULL; + + sma = kvzalloc_flex(*sma, sems, nsems, GFP_KERNEL_ACCOUNT); + if (unlikely(!sma)) + return NULL; + + return sma; +} + +/** + * newary - Create a new semaphore set + * @ns: namespace + * @params: ptr to the structure that contains key, semflg and nsems + * + * Called with sem_ids.rwsem held (as a writer) + */ +static int newary(struct ipc_namespace *ns, struct ipc_params *params) +{ + int retval; + struct sem_array *sma; + key_t key = params->key; + int nsems = params->u.nsems; + int semflg = params->flg; + int i; + + if (!nsems) + return -EINVAL; + if (ns->used_sems + nsems > ns->sc_semmns) + return -ENOSPC; + + sma = sem_alloc(nsems); + if (!sma) + return -ENOMEM; + + sma->sem_perm.mode = (semflg & S_IRWXUGO); + sma->sem_perm.key = key; + + sma->sem_perm.security = NULL; + retval = security_sem_alloc(&sma->sem_perm); + if (retval) { + kvfree(sma); + return retval; + } + + for (i = 0; i < nsems; i++) { + INIT_LIST_HEAD(&sma->sems[i].pending_alter); + INIT_LIST_HEAD(&sma->sems[i].pending_const); + spin_lock_init(&sma->sems[i].lock); + } + + sma->complex_count = 0; + sma->use_global_lock = USE_GLOBAL_LOCK_HYSTERESIS; + INIT_LIST_HEAD(&sma->pending_alter); + INIT_LIST_HEAD(&sma->pending_const); + INIT_LIST_HEAD(&sma->list_id); + sma->sem_nsems = nsems; + sma->sem_ctime = ktime_get_real_seconds(); + + /* ipc_addid() locks sma upon success. */ + retval = ipc_addid(&sem_ids(ns), &sma->sem_perm, ns->sc_semmni); + if (retval < 0) { + ipc_rcu_putref(&sma->sem_perm, sem_rcu_free); + return retval; + } + ns->used_sems += nsems; + + sem_unlock(sma, -1); + rcu_read_unlock(); + + return sma->sem_perm.id; +} + + +/* + * Called with sem_ids.rwsem and ipcp locked. + */ +static int sem_more_checks(struct kern_ipc_perm *ipcp, struct ipc_params *params) +{ + struct sem_array *sma; + + sma = container_of(ipcp, struct sem_array, sem_perm); + if (params->u.nsems > sma->sem_nsems) + return -EINVAL; + + return 0; +} + +long ksys_semget(key_t key, int nsems, int semflg) +{ + struct ipc_namespace *ns; + static const struct ipc_ops sem_ops = { + .getnew = newary, + .associate = security_sem_associate, + .more_checks = sem_more_checks, + }; + struct ipc_params sem_params; + + ns = current->nsproxy->ipc_ns; + + if (nsems < 0 || nsems > ns->sc_semmsl) + return -EINVAL; + + sem_params.key = key; + sem_params.flg = semflg; + sem_params.u.nsems = nsems; + + return ipcget(ns, &sem_ids(ns), &sem_ops, &sem_params); +} + +SYSCALL_DEFINE3(semget, key_t, key, int, nsems, int, semflg) +{ + return ksys_semget(key, nsems, semflg); +} + +/** + * perform_atomic_semop[_slow] - Attempt to perform semaphore + * operations on a given array. + * @sma: semaphore array + * @q: struct sem_queue that describes the operation + * + * Caller blocking are as follows, based the value + * indicated by the semaphore operation (sem_op): + * + * (1) >0 never blocks. + * (2) 0 (wait-for-zero operation): semval is non-zero. + * (3) <0 attempting to decrement semval to a value smaller than zero. + * + * Returns 0 if the operation was possible. + * Returns 1 if the operation is impossible, the caller must sleep. + * Returns <0 for error codes. + */ +static int perform_atomic_semop_slow(struct sem_array *sma, struct sem_queue *q) +{ + int result, sem_op, nsops; + struct pid *pid; + struct sembuf *sop; + struct sem *curr; + struct sembuf *sops; + struct sem_undo *un; + + sops = q->sops; + nsops = q->nsops; + un = q->undo; + + for (sop = sops; sop < sops + nsops; sop++) { + int idx = array_index_nospec(sop->sem_num, sma->sem_nsems); + curr = &sma->sems[idx]; + sem_op = sop->sem_op; + result = curr->semval; + + if (!sem_op && result) + goto would_block; + + result += sem_op; + if (result < 0) + goto would_block; + if (result > SEMVMX) + goto out_of_range; + + if (sop->sem_flg & SEM_UNDO) { + int undo = un->semadj[sop->sem_num] - sem_op; + /* Exceeding the undo range is an error. */ + if (undo < (-SEMAEM - 1) || undo > SEMAEM) + goto out_of_range; + un->semadj[sop->sem_num] = undo; + } + + curr->semval = result; + } + + sop--; + pid = q->pid; + while (sop >= sops) { + ipc_update_pid(&sma->sems[sop->sem_num].sempid, pid); + sop--; + } + + return 0; + +out_of_range: + result = -ERANGE; + goto undo; + +would_block: + q->blocking = sop; + + if (sop->sem_flg & IPC_NOWAIT) + result = -EAGAIN; + else + result = 1; + +undo: + sop--; + while (sop >= sops) { + sem_op = sop->sem_op; + sma->sems[sop->sem_num].semval -= sem_op; + if (sop->sem_flg & SEM_UNDO) + un->semadj[sop->sem_num] += sem_op; + sop--; + } + + return result; +} + +static int perform_atomic_semop(struct sem_array *sma, struct sem_queue *q) +{ + int result, sem_op, nsops; + struct sembuf *sop; + struct sem *curr; + struct sembuf *sops; + struct sem_undo *un; + + sops = q->sops; + nsops = q->nsops; + un = q->undo; + + if (unlikely(q->dupsop)) + return perform_atomic_semop_slow(sma, q); + + /* + * We scan the semaphore set twice, first to ensure that the entire + * operation can succeed, therefore avoiding any pointless writes + * to shared memory and having to undo such changes in order to block + * until the operations can go through. + */ + for (sop = sops; sop < sops + nsops; sop++) { + int idx = array_index_nospec(sop->sem_num, sma->sem_nsems); + + curr = &sma->sems[idx]; + sem_op = sop->sem_op; + result = curr->semval; + + if (!sem_op && result) + goto would_block; /* wait-for-zero */ + + result += sem_op; + if (result < 0) + goto would_block; + + if (result > SEMVMX) + return -ERANGE; + + if (sop->sem_flg & SEM_UNDO) { + int undo = un->semadj[sop->sem_num] - sem_op; + + /* Exceeding the undo range is an error. */ + if (undo < (-SEMAEM - 1) || undo > SEMAEM) + return -ERANGE; + } + } + + for (sop = sops; sop < sops + nsops; sop++) { + curr = &sma->sems[sop->sem_num]; + sem_op = sop->sem_op; + + if (sop->sem_flg & SEM_UNDO) { + int undo = un->semadj[sop->sem_num] - sem_op; + + un->semadj[sop->sem_num] = undo; + } + curr->semval += sem_op; + ipc_update_pid(&curr->sempid, q->pid); + } + + return 0; + +would_block: + q->blocking = sop; + return sop->sem_flg & IPC_NOWAIT ? -EAGAIN : 1; +} + +static inline void wake_up_sem_queue_prepare(struct sem_queue *q, int error, + struct wake_q_head *wake_q) +{ + struct task_struct *sleeper; + + sleeper = get_task_struct(q->sleeper); + + /* see SEM_BARRIER_2 for purpose/pairing */ + smp_store_release(&q->status, error); + + wake_q_add_safe(wake_q, sleeper); +} + +static void unlink_queue(struct sem_array *sma, struct sem_queue *q) +{ + list_del(&q->list); + if (q->nsops > 1) + sma->complex_count--; +} + +/** check_restart(sma, q) + * @sma: semaphore array + * @q: the operation that just completed + * + * update_queue is O(N^2) when it restarts scanning the whole queue of + * waiting operations. Therefore this function checks if the restart is + * really necessary. It is called after a previously waiting operation + * modified the array. + * Note that wait-for-zero operations are handled without restart. + */ +static inline int check_restart(struct sem_array *sma, struct sem_queue *q) +{ + /* pending complex alter operations are too difficult to analyse */ + if (!list_empty(&sma->pending_alter)) + return 1; + + /* we were a sleeping complex operation. Too difficult */ + if (q->nsops > 1) + return 1; + + /* It is impossible that someone waits for the new value: + * - complex operations always restart. + * - wait-for-zero are handled separately. + * - q is a previously sleeping simple operation that + * altered the array. It must be a decrement, because + * simple increments never sleep. + * - If there are older (higher priority) decrements + * in the queue, then they have observed the original + * semval value and couldn't proceed. The operation + * decremented to value - thus they won't proceed either. + */ + return 0; +} + +/** + * wake_const_ops - wake up non-alter tasks + * @sma: semaphore array. + * @semnum: semaphore that was modified. + * @wake_q: lockless wake-queue head. + * + * wake_const_ops must be called after a semaphore in a semaphore array + * was set to 0. If complex const operations are pending, wake_const_ops must + * be called with semnum = -1, as well as with the number of each modified + * semaphore. + * The tasks that must be woken up are added to @wake_q. The return code + * is stored in q->pid. + * The function returns 1 if at least one operation was completed successfully. + */ +static int wake_const_ops(struct sem_array *sma, int semnum, + struct wake_q_head *wake_q) +{ + struct sem_queue *q, *tmp; + struct list_head *pending_list; + int semop_completed = 0; + + if (semnum == -1) + pending_list = &sma->pending_const; + else + pending_list = &sma->sems[semnum].pending_const; + + list_for_each_entry_safe(q, tmp, pending_list, list) { + int error = perform_atomic_semop(sma, q); + + if (error > 0) + continue; + /* operation completed, remove from queue & wakeup */ + unlink_queue(sma, q); + + wake_up_sem_queue_prepare(q, error, wake_q); + if (error == 0) + semop_completed = 1; + } + + return semop_completed; +} + +/** + * do_smart_wakeup_zero - wakeup all wait for zero tasks + * @sma: semaphore array + * @sops: operations that were performed + * @nsops: number of operations + * @wake_q: lockless wake-queue head + * + * Checks all required queue for wait-for-zero operations, based + * on the actual changes that were performed on the semaphore array. + * The function returns 1 if at least one operation was completed successfully. + */ +static int do_smart_wakeup_zero(struct sem_array *sma, struct sembuf *sops, + int nsops, struct wake_q_head *wake_q) +{ + int i; + int semop_completed = 0; + int got_zero = 0; + + /* first: the per-semaphore queues, if known */ + if (sops) { + for (i = 0; i < nsops; i++) { + int num = sops[i].sem_num; + + if (sma->sems[num].semval == 0) { + got_zero = 1; + semop_completed |= wake_const_ops(sma, num, wake_q); + } + } + } else { + /* + * No sops means modified semaphores not known. + * Assume all were changed. + */ + for (i = 0; i < sma->sem_nsems; i++) { + if (sma->sems[i].semval == 0) { + got_zero = 1; + semop_completed |= wake_const_ops(sma, i, wake_q); + } + } + } + /* + * If one of the modified semaphores got 0, + * then check the global queue, too. + */ + if (got_zero) + semop_completed |= wake_const_ops(sma, -1, wake_q); + + return semop_completed; +} + + +/** + * update_queue - look for tasks that can be completed. + * @sma: semaphore array. + * @semnum: semaphore that was modified. + * @wake_q: lockless wake-queue head. + * + * update_queue must be called after a semaphore in a semaphore array + * was modified. If multiple semaphores were modified, update_queue must + * be called with semnum = -1, as well as with the number of each modified + * semaphore. + * The tasks that must be woken up are added to @wake_q. The return code + * is stored in q->pid. + * The function internally checks if const operations can now succeed. + * + * The function return 1 if at least one semop was completed successfully. + */ +static int update_queue(struct sem_array *sma, int semnum, struct wake_q_head *wake_q) +{ + struct sem_queue *q, *tmp; + struct list_head *pending_list; + int semop_completed = 0; + + if (semnum == -1) + pending_list = &sma->pending_alter; + else + pending_list = &sma->sems[semnum].pending_alter; + +again: + list_for_each_entry_safe(q, tmp, pending_list, list) { + int error, restart; + + /* If we are scanning the single sop, per-semaphore list of + * one semaphore and that semaphore is 0, then it is not + * necessary to scan further: simple increments + * that affect only one entry succeed immediately and cannot + * be in the per semaphore pending queue, and decrements + * cannot be successful if the value is already 0. + */ + if (semnum != -1 && sma->sems[semnum].semval == 0) + break; + + error = perform_atomic_semop(sma, q); + + /* Does q->sleeper still need to sleep? */ + if (error > 0) + continue; + + unlink_queue(sma, q); + + if (error) { + restart = 0; + } else { + semop_completed = 1; + do_smart_wakeup_zero(sma, q->sops, q->nsops, wake_q); + restart = check_restart(sma, q); + } + + wake_up_sem_queue_prepare(q, error, wake_q); + if (restart) + goto again; + } + return semop_completed; +} + +/** + * set_semotime - set sem_otime + * @sma: semaphore array + * @sops: operations that modified the array, may be NULL + * + * sem_otime is replicated to avoid cache line trashing. + * This function sets one instance to the current time. + */ +static void set_semotime(struct sem_array *sma, struct sembuf *sops) +{ + if (sops == NULL) { + sma->sems[0].sem_otime = ktime_get_real_seconds(); + } else { + sma->sems[sops[0].sem_num].sem_otime = + ktime_get_real_seconds(); + } +} + +/** + * do_smart_update - optimized update_queue + * @sma: semaphore array + * @sops: operations that were performed + * @nsops: number of operations + * @otime: force setting otime + * @wake_q: lockless wake-queue head + * + * do_smart_update() does the required calls to update_queue and wakeup_zero, + * based on the actual changes that were performed on the semaphore array. + * Note that the function does not do the actual wake-up: the caller is + * responsible for calling wake_up_q(). + * It is safe to perform this call after dropping all locks. + */ +static void do_smart_update(struct sem_array *sma, struct sembuf *sops, int nsops, + int otime, struct wake_q_head *wake_q) +{ + int i; + + otime |= do_smart_wakeup_zero(sma, sops, nsops, wake_q); + + if (!list_empty(&sma->pending_alter)) { + /* semaphore array uses the global queue - just process it. */ + otime |= update_queue(sma, -1, wake_q); + } else { + if (!sops) { + /* + * No sops, thus the modified semaphores are not + * known. Check all. + */ + for (i = 0; i < sma->sem_nsems; i++) + otime |= update_queue(sma, i, wake_q); + } else { + /* + * Check the semaphores that were increased: + * - No complex ops, thus all sleeping ops are + * decrease. + * - if we decreased the value, then any sleeping + * semaphore ops won't be able to run: If the + * previous value was too small, then the new + * value will be too small, too. + */ + for (i = 0; i < nsops; i++) { + if (sops[i].sem_op > 0) { + otime |= update_queue(sma, + sops[i].sem_num, wake_q); + } + } + } + } + if (otime) + set_semotime(sma, sops); +} + +/* + * check_qop: Test if a queued operation sleeps on the semaphore semnum + */ +static int check_qop(struct sem_array *sma, int semnum, struct sem_queue *q, + bool count_zero) +{ + struct sembuf *sop = q->blocking; + + /* + * Linux always (since 0.99.10) reported a task as sleeping on all + * semaphores. This violates SUS, therefore it was changed to the + * standard compliant behavior. + * Give the administrators a chance to notice that an application + * might misbehave because it relies on the Linux behavior. + */ + pr_info_once("semctl(GETNCNT/GETZCNT) is since 3.16 Single Unix Specification compliant.\n" + "The task %s (%d) triggered the difference, watch for misbehavior.\n", + current->comm, task_pid_nr(current)); + + if (sop->sem_num != semnum) + return 0; + + if (count_zero && sop->sem_op == 0) + return 1; + if (!count_zero && sop->sem_op < 0) + return 1; + + return 0; +} + +/* The following counts are associated to each semaphore: + * semncnt number of tasks waiting on semval being nonzero + * semzcnt number of tasks waiting on semval being zero + * + * Per definition, a task waits only on the semaphore of the first semop + * that cannot proceed, even if additional operation would block, too. + */ +static int count_semcnt(struct sem_array *sma, ushort semnum, + bool count_zero) +{ + struct list_head *l; + struct sem_queue *q; + int semcnt; + + semcnt = 0; + /* First: check the simple operations. They are easy to evaluate */ + if (count_zero) + l = &sma->sems[semnum].pending_const; + else + l = &sma->sems[semnum].pending_alter; + + list_for_each_entry(q, l, list) { + /* all task on a per-semaphore list sleep on exactly + * that semaphore + */ + semcnt++; + } + + /* Then: check the complex operations. */ + list_for_each_entry(q, &sma->pending_alter, list) { + semcnt += check_qop(sma, semnum, q, count_zero); + } + if (count_zero) { + list_for_each_entry(q, &sma->pending_const, list) { + semcnt += check_qop(sma, semnum, q, count_zero); + } + } + return semcnt; +} + +/* Free a semaphore set. freeary() is called with sem_ids.rwsem locked + * as a writer and the spinlock for this semaphore set hold. sem_ids.rwsem + * remains locked on exit. + */ +static void freeary(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp) +{ + struct sem_undo *un, *tu; + struct sem_queue *q, *tq; + struct sem_array *sma = container_of(ipcp, struct sem_array, sem_perm); + int i; + DEFINE_WAKE_Q(wake_q); + + /* Free the existing undo structures for this semaphore set. */ + ipc_assert_locked_object(&sma->sem_perm); + list_for_each_entry_safe(un, tu, &sma->list_id, list_id) { + list_del(&un->list_id); + spin_lock(&un->ulp->lock); + un->semid = -1; + list_del_rcu(&un->list_proc); + spin_unlock(&un->ulp->lock); + kvfree_rcu(un, rcu); + } + + /* Wake up all pending processes and let them fail with EIDRM. */ + list_for_each_entry_safe(q, tq, &sma->pending_const, list) { + unlink_queue(sma, q); + wake_up_sem_queue_prepare(q, -EIDRM, &wake_q); + } + + list_for_each_entry_safe(q, tq, &sma->pending_alter, list) { + unlink_queue(sma, q); + wake_up_sem_queue_prepare(q, -EIDRM, &wake_q); + } + for (i = 0; i < sma->sem_nsems; i++) { + struct sem *sem = &sma->sems[i]; + list_for_each_entry_safe(q, tq, &sem->pending_const, list) { + unlink_queue(sma, q); + wake_up_sem_queue_prepare(q, -EIDRM, &wake_q); + } + list_for_each_entry_safe(q, tq, &sem->pending_alter, list) { + unlink_queue(sma, q); + wake_up_sem_queue_prepare(q, -EIDRM, &wake_q); + } + ipc_update_pid(&sem->sempid, NULL); + } + + /* Remove the semaphore set from the IDR */ + sem_rmid(ns, sma); + sem_unlock(sma, -1); + rcu_read_unlock(); + + wake_up_q(&wake_q); + ns->used_sems -= sma->sem_nsems; + ipc_rcu_putref(&sma->sem_perm, sem_rcu_free); +} + +static unsigned long copy_semid_to_user(void __user *buf, struct semid64_ds *in, int version) +{ + switch (version) { + case IPC_64: + return copy_to_user(buf, in, sizeof(*in)); + case IPC_OLD: + { + struct semid_ds out; + + memset(&out, 0, sizeof(out)); + + ipc64_perm_to_ipc_perm(&in->sem_perm, &out.sem_perm); + + out.sem_otime = in->sem_otime; + out.sem_ctime = in->sem_ctime; + out.sem_nsems = in->sem_nsems; + + return copy_to_user(buf, &out, sizeof(out)); + } + default: + return -EINVAL; + } +} + +static time64_t get_semotime(struct sem_array *sma) +{ + int i; + time64_t res; + + res = sma->sems[0].sem_otime; + for (i = 1; i < sma->sem_nsems; i++) { + time64_t to = sma->sems[i].sem_otime; + + if (to > res) + res = to; + } + return res; +} + +static int semctl_stat(struct ipc_namespace *ns, int semid, + int cmd, struct semid64_ds *semid64) +{ + struct sem_array *sma; + time64_t semotime; + int err; + + memset(semid64, 0, sizeof(*semid64)); + + rcu_read_lock(); + if (cmd == SEM_STAT || cmd == SEM_STAT_ANY) { + sma = sem_obtain_object(ns, semid); + if (IS_ERR(sma)) { + err = PTR_ERR(sma); + goto out_unlock; + } + } else { /* IPC_STAT */ + sma = sem_obtain_object_check(ns, semid); + if (IS_ERR(sma)) { + err = PTR_ERR(sma); + goto out_unlock; + } + } + + /* see comment for SHM_STAT_ANY */ + if (cmd == SEM_STAT_ANY) + audit_ipc_obj(&sma->sem_perm); + else { + err = -EACCES; + if (ipcperms(ns, &sma->sem_perm, S_IRUGO)) + goto out_unlock; + } + + err = security_sem_semctl(&sma->sem_perm, cmd); + if (err) + goto out_unlock; + + ipc_lock_object(&sma->sem_perm); + + if (!ipc_valid_object(&sma->sem_perm)) { + ipc_unlock_object(&sma->sem_perm); + err = -EIDRM; + goto out_unlock; + } + + kernel_to_ipc64_perm(&sma->sem_perm, &semid64->sem_perm); + semotime = get_semotime(sma); + semid64->sem_otime = semotime; + semid64->sem_ctime = sma->sem_ctime; +#ifndef CONFIG_64BIT + semid64->sem_otime_high = semotime >> 32; + semid64->sem_ctime_high = sma->sem_ctime >> 32; +#endif + semid64->sem_nsems = sma->sem_nsems; + + if (cmd == IPC_STAT) { + /* + * As defined in SUS: + * Return 0 on success + */ + err = 0; + } else { + /* + * SEM_STAT and SEM_STAT_ANY (both Linux specific) + * Return the full id, including the sequence number + */ + err = sma->sem_perm.id; + } + ipc_unlock_object(&sma->sem_perm); +out_unlock: + rcu_read_unlock(); + return err; +} + +static int semctl_info(struct ipc_namespace *ns, int semid, + int cmd, void __user *p) +{ + struct seminfo seminfo; + int max_idx; + int err; + + err = security_sem_semctl(NULL, cmd); + if (err) + return err; + + memset(&seminfo, 0, sizeof(seminfo)); + seminfo.semmni = ns->sc_semmni; + seminfo.semmns = ns->sc_semmns; + seminfo.semmsl = ns->sc_semmsl; + seminfo.semopm = ns->sc_semopm; + seminfo.semvmx = SEMVMX; + seminfo.semmnu = SEMMNU; + seminfo.semmap = SEMMAP; + seminfo.semume = SEMUME; + down_read(&sem_ids(ns).rwsem); + if (cmd == SEM_INFO) { + seminfo.semusz = sem_ids(ns).in_use; + seminfo.semaem = ns->used_sems; + } else { + seminfo.semusz = SEMUSZ; + seminfo.semaem = SEMAEM; + } + max_idx = ipc_get_maxidx(&sem_ids(ns)); + up_read(&sem_ids(ns).rwsem); + if (copy_to_user(p, &seminfo, sizeof(struct seminfo))) + return -EFAULT; + return (max_idx < 0) ? 0 : max_idx; +} + +static int semctl_setval(struct ipc_namespace *ns, int semid, int semnum, + int val) +{ + struct sem_undo *un; + struct sem_array *sma; + struct sem *curr; + int err; + DEFINE_WAKE_Q(wake_q); + + if (val > SEMVMX || val < 0) + return -ERANGE; + + rcu_read_lock(); + sma = sem_obtain_object_check(ns, semid); + if (IS_ERR(sma)) { + rcu_read_unlock(); + return PTR_ERR(sma); + } + + if (semnum < 0 || semnum >= sma->sem_nsems) { + rcu_read_unlock(); + return -EINVAL; + } + + + if (ipcperms(ns, &sma->sem_perm, S_IWUGO)) { + rcu_read_unlock(); + return -EACCES; + } + + err = security_sem_semctl(&sma->sem_perm, SETVAL); + if (err) { + rcu_read_unlock(); + return -EACCES; + } + + sem_lock(sma, NULL, -1); + + if (!ipc_valid_object(&sma->sem_perm)) { + sem_unlock(sma, -1); + rcu_read_unlock(); + return -EIDRM; + } + + semnum = array_index_nospec(semnum, sma->sem_nsems); + curr = &sma->sems[semnum]; + + ipc_assert_locked_object(&sma->sem_perm); + list_for_each_entry(un, &sma->list_id, list_id) + un->semadj[semnum] = 0; + + curr->semval = val; + ipc_update_pid(&curr->sempid, task_tgid(current)); + sma->sem_ctime = ktime_get_real_seconds(); + /* maybe some queued-up processes were waiting for this */ + do_smart_update(sma, NULL, 0, 0, &wake_q); + sem_unlock(sma, -1); + rcu_read_unlock(); + wake_up_q(&wake_q); + return 0; +} + +static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, + int cmd, void __user *p) +{ + struct sem_array *sma; + struct sem *curr; + int err, nsems; + ushort fast_sem_io[SEMMSL_FAST]; + ushort *sem_io = fast_sem_io; + DEFINE_WAKE_Q(wake_q); + + rcu_read_lock(); + sma = sem_obtain_object_check(ns, semid); + if (IS_ERR(sma)) { + rcu_read_unlock(); + return PTR_ERR(sma); + } + + nsems = sma->sem_nsems; + + err = -EACCES; + if (ipcperms(ns, &sma->sem_perm, cmd == SETALL ? S_IWUGO : S_IRUGO)) + goto out_rcu_wakeup; + + err = security_sem_semctl(&sma->sem_perm, cmd); + if (err) + goto out_rcu_wakeup; + + switch (cmd) { + case GETALL: + { + ushort __user *array = p; + int i; + + sem_lock(sma, NULL, -1); + if (!ipc_valid_object(&sma->sem_perm)) { + err = -EIDRM; + goto out_unlock; + } + if (nsems > SEMMSL_FAST) { + if (!ipc_rcu_getref(&sma->sem_perm)) { + err = -EIDRM; + goto out_unlock; + } + sem_unlock(sma, -1); + rcu_read_unlock(); + sem_io = kvmalloc_array(nsems, sizeof(ushort), + GFP_KERNEL); + if (sem_io == NULL) { + ipc_rcu_putref(&sma->sem_perm, sem_rcu_free); + return -ENOMEM; + } + + rcu_read_lock(); + sem_lock_and_putref(sma); + if (!ipc_valid_object(&sma->sem_perm)) { + err = -EIDRM; + goto out_unlock; + } + } + for (i = 0; i < sma->sem_nsems; i++) + sem_io[i] = sma->sems[i].semval; + sem_unlock(sma, -1); + rcu_read_unlock(); + err = 0; + if (copy_to_user(array, sem_io, nsems*sizeof(ushort))) + err = -EFAULT; + goto out_free; + } + case SETALL: + { + int i; + struct sem_undo *un; + + if (!ipc_rcu_getref(&sma->sem_perm)) { + err = -EIDRM; + goto out_rcu_wakeup; + } + rcu_read_unlock(); + + if (nsems > SEMMSL_FAST) { + sem_io = kvmalloc_array(nsems, sizeof(ushort), + GFP_KERNEL); + if (sem_io == NULL) { + ipc_rcu_putref(&sma->sem_perm, sem_rcu_free); + return -ENOMEM; + } + } + + if (copy_from_user(sem_io, p, nsems*sizeof(ushort))) { + ipc_rcu_putref(&sma->sem_perm, sem_rcu_free); + err = -EFAULT; + goto out_free; + } + + for (i = 0; i < nsems; i++) { + if (sem_io[i] > SEMVMX) { + ipc_rcu_putref(&sma->sem_perm, sem_rcu_free); + err = -ERANGE; + goto out_free; + } + } + rcu_read_lock(); + sem_lock_and_putref(sma); + if (!ipc_valid_object(&sma->sem_perm)) { + err = -EIDRM; + goto out_unlock; + } + + for (i = 0; i < nsems; i++) { + sma->sems[i].semval = sem_io[i]; + ipc_update_pid(&sma->sems[i].sempid, task_tgid(current)); + } + + ipc_assert_locked_object(&sma->sem_perm); + list_for_each_entry(un, &sma->list_id, list_id) { + for (i = 0; i < nsems; i++) + un->semadj[i] = 0; + } + sma->sem_ctime = ktime_get_real_seconds(); + /* maybe some queued-up processes were waiting for this */ + do_smart_update(sma, NULL, 0, 0, &wake_q); + err = 0; + goto out_unlock; + } + /* GETVAL, GETPID, GETNCTN, GETZCNT: fall-through */ + } + err = -EINVAL; + if (semnum < 0 || semnum >= nsems) + goto out_rcu_wakeup; + + sem_lock(sma, NULL, -1); + if (!ipc_valid_object(&sma->sem_perm)) { + err = -EIDRM; + goto out_unlock; + } + + semnum = array_index_nospec(semnum, nsems); + curr = &sma->sems[semnum]; + + switch (cmd) { + case GETVAL: + err = curr->semval; + goto out_unlock; + case GETPID: + err = pid_vnr(curr->sempid); + goto out_unlock; + case GETNCNT: + err = count_semcnt(sma, semnum, 0); + goto out_unlock; + case GETZCNT: + err = count_semcnt(sma, semnum, 1); + goto out_unlock; + } + +out_unlock: + sem_unlock(sma, -1); +out_rcu_wakeup: + rcu_read_unlock(); + wake_up_q(&wake_q); +out_free: + if (sem_io != fast_sem_io) + kvfree(sem_io); + return err; +} + +static inline unsigned long +copy_semid_from_user(struct semid64_ds *out, void __user *buf, int version) +{ + switch (version) { + case IPC_64: + if (copy_from_user(out, buf, sizeof(*out))) + return -EFAULT; + return 0; + case IPC_OLD: + { + struct semid_ds tbuf_old; + + if (copy_from_user(&tbuf_old, buf, sizeof(tbuf_old))) + return -EFAULT; + + out->sem_perm.uid = tbuf_old.sem_perm.uid; + out->sem_perm.gid = tbuf_old.sem_perm.gid; + out->sem_perm.mode = tbuf_old.sem_perm.mode; + + return 0; + } + default: + return -EINVAL; + } +} + +/* + * This function handles some semctl commands which require the rwsem + * to be held in write mode. + * NOTE: no locks must be held, the rwsem is taken inside this function. + */ +static int semctl_down(struct ipc_namespace *ns, int semid, + int cmd, struct semid64_ds *semid64) +{ + struct sem_array *sma; + int err; + struct kern_ipc_perm *ipcp; + + down_write(&sem_ids(ns).rwsem); + rcu_read_lock(); + + ipcp = ipcctl_obtain_check(ns, &sem_ids(ns), semid, cmd, + &semid64->sem_perm, 0); + if (IS_ERR(ipcp)) { + err = PTR_ERR(ipcp); + goto out_unlock1; + } + + sma = container_of(ipcp, struct sem_array, sem_perm); + + err = security_sem_semctl(&sma->sem_perm, cmd); + if (err) + goto out_unlock1; + + switch (cmd) { + case IPC_RMID: + sem_lock(sma, NULL, -1); + /* freeary unlocks the ipc object and rcu */ + freeary(ns, ipcp); + goto out_up; + case IPC_SET: + sem_lock(sma, NULL, -1); + err = ipc_update_perm(&semid64->sem_perm, ipcp); + if (err) + goto out_unlock0; + sma->sem_ctime = ktime_get_real_seconds(); + break; + default: + err = -EINVAL; + goto out_unlock1; + } + +out_unlock0: + sem_unlock(sma, -1); +out_unlock1: + rcu_read_unlock(); +out_up: + up_write(&sem_ids(ns).rwsem); + return err; +} + +static long ksys_semctl(int semid, int semnum, int cmd, unsigned long arg, int version) +{ + struct ipc_namespace *ns; + void __user *p = (void __user *)arg; + struct semid64_ds semid64; + int err; + + if (semid < 0) + return -EINVAL; + + ns = current->nsproxy->ipc_ns; + + switch (cmd) { + case IPC_INFO: + case SEM_INFO: + return semctl_info(ns, semid, cmd, p); + case IPC_STAT: + case SEM_STAT: + case SEM_STAT_ANY: + err = semctl_stat(ns, semid, cmd, &semid64); + if (err < 0) + return err; + if (copy_semid_to_user(p, &semid64, version)) + err = -EFAULT; + return err; + case GETALL: + case GETVAL: + case GETPID: + case GETNCNT: + case GETZCNT: + case SETALL: + return semctl_main(ns, semid, semnum, cmd, p); + case SETVAL: { + int val; +#if defined(CONFIG_64BIT) && defined(__BIG_ENDIAN) + /* big-endian 64bit */ + val = arg >> 32; +#else + /* 32bit or little-endian 64bit */ + val = arg; +#endif + return semctl_setval(ns, semid, semnum, val); + } + case IPC_SET: + if (copy_semid_from_user(&semid64, p, version)) + return -EFAULT; + fallthrough; + case IPC_RMID: + return semctl_down(ns, semid, cmd, &semid64); + default: + return -EINVAL; + } +} + +SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, unsigned long, arg) +{ + return ksys_semctl(semid, semnum, cmd, arg, IPC_64); +} + +#ifdef CONFIG_ARCH_WANT_IPC_PARSE_VERSION +long ksys_old_semctl(int semid, int semnum, int cmd, unsigned long arg) +{ + int version = ipc_parse_version(&cmd); + + return ksys_semctl(semid, semnum, cmd, arg, version); +} + +SYSCALL_DEFINE4(old_semctl, int, semid, int, semnum, int, cmd, unsigned long, arg) +{ + return ksys_old_semctl(semid, semnum, cmd, arg); +} +#endif + +#ifdef CONFIG_COMPAT + +struct compat_semid_ds { + struct compat_ipc_perm sem_perm; + old_time32_t sem_otime; + old_time32_t sem_ctime; + compat_uptr_t sem_base; + compat_uptr_t sem_pending; + compat_uptr_t sem_pending_last; + compat_uptr_t undo; + unsigned short sem_nsems; +}; + +static int copy_compat_semid_from_user(struct semid64_ds *out, void __user *buf, + int version) +{ + memset(out, 0, sizeof(*out)); + if (version == IPC_64) { + struct compat_semid64_ds __user *p = buf; + return get_compat_ipc64_perm(&out->sem_perm, &p->sem_perm); + } else { + struct compat_semid_ds __user *p = buf; + return get_compat_ipc_perm(&out->sem_perm, &p->sem_perm); + } +} + +static int copy_compat_semid_to_user(void __user *buf, struct semid64_ds *in, + int version) +{ + if (version == IPC_64) { + struct compat_semid64_ds v; + memset(&v, 0, sizeof(v)); + to_compat_ipc64_perm(&v.sem_perm, &in->sem_perm); + v.sem_otime = lower_32_bits(in->sem_otime); + v.sem_otime_high = upper_32_bits(in->sem_otime); + v.sem_ctime = lower_32_bits(in->sem_ctime); + v.sem_ctime_high = upper_32_bits(in->sem_ctime); + v.sem_nsems = in->sem_nsems; + return copy_to_user(buf, &v, sizeof(v)); + } else { + struct compat_semid_ds v; + memset(&v, 0, sizeof(v)); + to_compat_ipc_perm(&v.sem_perm, &in->sem_perm); + v.sem_otime = in->sem_otime; + v.sem_ctime = in->sem_ctime; + v.sem_nsems = in->sem_nsems; + return copy_to_user(buf, &v, sizeof(v)); + } +} + +static long compat_ksys_semctl(int semid, int semnum, int cmd, int arg, int version) +{ + void __user *p = compat_ptr(arg); + struct ipc_namespace *ns; + struct semid64_ds semid64; + int err; + + ns = current->nsproxy->ipc_ns; + + if (semid < 0) + return -EINVAL; + + switch (cmd & (~IPC_64)) { + case IPC_INFO: + case SEM_INFO: + return semctl_info(ns, semid, cmd, p); + case IPC_STAT: + case SEM_STAT: + case SEM_STAT_ANY: + err = semctl_stat(ns, semid, cmd, &semid64); + if (err < 0) + return err; + if (copy_compat_semid_to_user(p, &semid64, version)) + err = -EFAULT; + return err; + case GETVAL: + case GETPID: + case GETNCNT: + case GETZCNT: + case GETALL: + case SETALL: + return semctl_main(ns, semid, semnum, cmd, p); + case SETVAL: + return semctl_setval(ns, semid, semnum, arg); + case IPC_SET: + if (copy_compat_semid_from_user(&semid64, p, version)) + return -EFAULT; + fallthrough; + case IPC_RMID: + return semctl_down(ns, semid, cmd, &semid64); + default: + return -EINVAL; + } +} + +COMPAT_SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, int, arg) +{ + return compat_ksys_semctl(semid, semnum, cmd, arg, IPC_64); +} + +#ifdef CONFIG_ARCH_WANT_COMPAT_IPC_PARSE_VERSION +long compat_ksys_old_semctl(int semid, int semnum, int cmd, int arg) +{ + int version = compat_ipc_parse_version(&cmd); + + return compat_ksys_semctl(semid, semnum, cmd, arg, version); +} + +COMPAT_SYSCALL_DEFINE4(old_semctl, int, semid, int, semnum, int, cmd, int, arg) +{ + return compat_ksys_old_semctl(semid, semnum, cmd, arg); +} +#endif +#endif + +/* If the task doesn't already have a undo_list, then allocate one + * here. We guarantee there is only one thread using this undo list, + * and current is THE ONE + * + * If this allocation and assignment succeeds, but later + * portions of this code fail, there is no need to free the sem_undo_list. + * Just let it stay associated with the task, and it'll be freed later + * at exit time. + * + * This can block, so callers must hold no locks. + */ +static inline int get_undo_list(struct sem_undo_list **undo_listp) +{ + struct sem_undo_list *undo_list; + + undo_list = current->sysvsem.undo_list; + if (!undo_list) { + undo_list = kzalloc_obj(*undo_list, GFP_KERNEL_ACCOUNT); + if (undo_list == NULL) + return -ENOMEM; + spin_lock_init(&undo_list->lock); + refcount_set(&undo_list->refcnt, 1); + INIT_LIST_HEAD(&undo_list->list_proc); + + current->sysvsem.undo_list = undo_list; + } + *undo_listp = undo_list; + return 0; +} + +static struct sem_undo *__lookup_undo(struct sem_undo_list *ulp, int semid) +{ + struct sem_undo *un; + + list_for_each_entry_rcu(un, &ulp->list_proc, list_proc, + spin_is_locked(&ulp->lock)) { + if (un->semid == semid) + return un; + } + return NULL; +} + +static struct sem_undo *lookup_undo(struct sem_undo_list *ulp, int semid) +{ + struct sem_undo *un; + + assert_spin_locked(&ulp->lock); + + un = __lookup_undo(ulp, semid); + if (un) { + list_del_rcu(&un->list_proc); + list_add_rcu(&un->list_proc, &ulp->list_proc); + } + return un; +} + +/** + * find_alloc_undo - lookup (and if not present create) undo array + * @ns: namespace + * @semid: semaphore array id + * + * The function looks up (and if not present creates) the undo structure. + * The size of the undo structure depends on the size of the semaphore + * array, thus the alloc path is not that straightforward. + * Lifetime-rules: sem_undo is rcu-protected, on success, the function + * performs a rcu_read_lock(). + */ +static struct sem_undo *find_alloc_undo(struct ipc_namespace *ns, int semid) +{ + struct sem_array *sma; + struct sem_undo_list *ulp; + struct sem_undo *un, *new; + int nsems, error; + + error = get_undo_list(&ulp); + if (error) + return ERR_PTR(error); + + rcu_read_lock(); + spin_lock(&ulp->lock); + un = lookup_undo(ulp, semid); + spin_unlock(&ulp->lock); + if (likely(un != NULL)) + goto out; + + /* no undo structure around - allocate one. */ + /* step 1: figure out the size of the semaphore array */ + sma = sem_obtain_object_check(ns, semid); + if (IS_ERR(sma)) { + rcu_read_unlock(); + return ERR_CAST(sma); + } + + nsems = sma->sem_nsems; + if (!ipc_rcu_getref(&sma->sem_perm)) { + rcu_read_unlock(); + un = ERR_PTR(-EIDRM); + goto out; + } + rcu_read_unlock(); + + /* step 2: allocate new undo structure */ + new = kvzalloc_flex(*new, semadj, nsems, GFP_KERNEL_ACCOUNT); + if (!new) { + ipc_rcu_putref(&sma->sem_perm, sem_rcu_free); + return ERR_PTR(-ENOMEM); + } + + /* step 3: Acquire the lock on semaphore array */ + rcu_read_lock(); + sem_lock_and_putref(sma); + if (!ipc_valid_object(&sma->sem_perm)) { + sem_unlock(sma, -1); + rcu_read_unlock(); + kvfree(new); + un = ERR_PTR(-EIDRM); + goto out; + } + spin_lock(&ulp->lock); + + /* + * step 4: check for races: did someone else allocate the undo struct? + */ + un = lookup_undo(ulp, semid); + if (un) { + spin_unlock(&ulp->lock); + kvfree(new); + goto success; + } + /* step 5: initialize & link new undo structure */ + new->ulp = ulp; + new->semid = semid; + assert_spin_locked(&ulp->lock); + list_add_rcu(&new->list_proc, &ulp->list_proc); + ipc_assert_locked_object(&sma->sem_perm); + list_add(&new->list_id, &sma->list_id); + un = new; + spin_unlock(&ulp->lock); +success: + sem_unlock(sma, -1); +out: + return un; +} + +long __do_semtimedop(int semid, struct sembuf *sops, + unsigned int nsops, const struct timespec64 *timeout, + struct ipc_namespace *ns) +{ + int error = -EINVAL; + struct sem_array *sma; + struct sembuf *sop; + struct sem_undo *un; + int max, locknum; + bool undos = false, alter = false, dupsop = false; + struct sem_queue queue; + unsigned long dup = 0; + ktime_t expires, *exp = NULL; + bool timed_out = false; + + if (nsops < 1 || semid < 0) + return -EINVAL; + if (nsops > ns->sc_semopm) + return -E2BIG; + + if (timeout) { + if (!timespec64_valid(timeout)) + return -EINVAL; + expires = ktime_add_safe(ktime_get(), + timespec64_to_ktime(*timeout)); + exp = &expires; + } + + + max = 0; + for (sop = sops; sop < sops + nsops; sop++) { + unsigned long mask = 1ULL << ((sop->sem_num) % BITS_PER_LONG); + + if (sop->sem_num >= max) + max = sop->sem_num; + if (sop->sem_flg & SEM_UNDO) + undos = true; + if (dup & mask) { + /* + * There was a previous alter access that appears + * to have accessed the same semaphore, thus use + * the dupsop logic. "appears", because the detection + * can only check % BITS_PER_LONG. + */ + dupsop = true; + } + if (sop->sem_op != 0) { + alter = true; + dup |= mask; + } + } + + if (undos) { + /* On success, find_alloc_undo takes the rcu_read_lock */ + un = find_alloc_undo(ns, semid); + if (IS_ERR(un)) { + error = PTR_ERR(un); + goto out; + } + } else { + un = NULL; + rcu_read_lock(); + } + + sma = sem_obtain_object_check(ns, semid); + if (IS_ERR(sma)) { + rcu_read_unlock(); + error = PTR_ERR(sma); + goto out; + } + + error = -EFBIG; + if (max >= sma->sem_nsems) { + rcu_read_unlock(); + goto out; + } + + error = -EACCES; + if (ipcperms(ns, &sma->sem_perm, alter ? S_IWUGO : S_IRUGO)) { + rcu_read_unlock(); + goto out; + } + + error = security_sem_semop(&sma->sem_perm, sops, nsops, alter); + if (error) { + rcu_read_unlock(); + goto out; + } + + error = -EIDRM; + locknum = sem_lock(sma, sops, nsops); + /* + * We eventually might perform the following check in a lockless + * fashion, considering ipc_valid_object() locking constraints. + * If nsops == 1 and there is no contention for sem_perm.lock, then + * only a per-semaphore lock is held and it's OK to proceed with the + * check below. More details on the fine grained locking scheme + * entangled here and why it's RMID race safe on comments at sem_lock() + */ + if (!ipc_valid_object(&sma->sem_perm)) + goto out_unlock; + /* + * semid identifiers are not unique - find_alloc_undo may have + * allocated an undo structure, it was invalidated by an RMID + * and now a new array with received the same id. Check and fail. + * This case can be detected checking un->semid. The existence of + * "un" itself is guaranteed by rcu. + */ + if (un && un->semid == -1) + goto out_unlock; + + queue.sops = sops; + queue.nsops = nsops; + queue.undo = un; + queue.pid = task_tgid(current); + queue.alter = alter; + queue.dupsop = dupsop; + + error = perform_atomic_semop(sma, &queue); + if (error == 0) { /* non-blocking successful path */ + DEFINE_WAKE_Q(wake_q); + + /* + * If the operation was successful, then do + * the required updates. + */ + if (alter) + do_smart_update(sma, sops, nsops, 1, &wake_q); + else + set_semotime(sma, sops); + + sem_unlock(sma, locknum); + rcu_read_unlock(); + wake_up_q(&wake_q); + + goto out; + } + if (error < 0) /* non-blocking error path */ + goto out_unlock; + + /* + * We need to sleep on this operation, so we put the current + * task into the pending queue and go to sleep. + */ + if (nsops == 1) { + struct sem *curr; + int idx = array_index_nospec(sops->sem_num, sma->sem_nsems); + curr = &sma->sems[idx]; + + if (alter) { + if (sma->complex_count) { + list_add_tail(&queue.list, + &sma->pending_alter); + } else { + + list_add_tail(&queue.list, + &curr->pending_alter); + } + } else { + list_add_tail(&queue.list, &curr->pending_const); + } + } else { + if (!sma->complex_count) + merge_queues(sma); + + if (alter) + list_add_tail(&queue.list, &sma->pending_alter); + else + list_add_tail(&queue.list, &sma->pending_const); + + sma->complex_count++; + } + + do { + /* memory ordering ensured by the lock in sem_lock() */ + WRITE_ONCE(queue.status, -EINTR); + queue.sleeper = current; + + /* memory ordering is ensured by the lock in sem_lock() */ + __set_current_state(TASK_INTERRUPTIBLE); + sem_unlock(sma, locknum); + rcu_read_unlock(); + + timed_out = !schedule_hrtimeout_range(exp, + current->timer_slack_ns, HRTIMER_MODE_ABS); + + /* + * fastpath: the semop has completed, either successfully or + * not, from the syscall pov, is quite irrelevant to us at this + * point; we're done. + * + * We _do_ care, nonetheless, about being awoken by a signal or + * spuriously. The queue.status is checked again in the + * slowpath (aka after taking sem_lock), such that we can detect + * scenarios where we were awakened externally, during the + * window between wake_q_add() and wake_up_q(). + */ + rcu_read_lock(); + error = READ_ONCE(queue.status); + if (error != -EINTR) { + /* see SEM_BARRIER_2 for purpose/pairing */ + smp_acquire__after_ctrl_dep(); + rcu_read_unlock(); + goto out; + } + + locknum = sem_lock(sma, sops, nsops); + + if (!ipc_valid_object(&sma->sem_perm)) + goto out_unlock; + + /* + * No necessity for any barrier: We are protect by sem_lock() + */ + error = READ_ONCE(queue.status); + + /* + * If queue.status != -EINTR we are woken up by another process. + * Leave without unlink_queue(), but with sem_unlock(). + */ + if (error != -EINTR) + goto out_unlock; + + /* + * If an interrupt occurred we have to clean up the queue. + */ + if (timed_out) + error = -EAGAIN; + } while (error == -EINTR && !signal_pending(current)); /* spurious */ + + unlink_queue(sma, &queue); + +out_unlock: + sem_unlock(sma, locknum); + rcu_read_unlock(); +out: + return error; +} + +static long do_semtimedop(int semid, struct sembuf __user *tsops, + unsigned int nsops, const struct timespec64 *timeout) +{ + struct sembuf fast_sops[SEMOPM_FAST]; + struct sembuf *sops = fast_sops; + struct ipc_namespace *ns; + int ret; + + ns = current->nsproxy->ipc_ns; + if (nsops > ns->sc_semopm) + return -E2BIG; + if (nsops < 1) + return -EINVAL; + + if (nsops > SEMOPM_FAST) { + sops = kvmalloc_objs(*sops, nsops); + if (sops == NULL) + return -ENOMEM; + } + + if (copy_from_user(sops, tsops, nsops * sizeof(*tsops))) { + ret = -EFAULT; + goto out_free; + } + + ret = __do_semtimedop(semid, sops, nsops, timeout, ns); + +out_free: + if (sops != fast_sops) + kvfree(sops); + + return ret; +} + +long ksys_semtimedop(int semid, struct sembuf __user *tsops, + unsigned int nsops, const struct __kernel_timespec __user *timeout) +{ + if (timeout) { + struct timespec64 ts; + if (get_timespec64(&ts, timeout)) + return -EFAULT; + return do_semtimedop(semid, tsops, nsops, &ts); + } + return do_semtimedop(semid, tsops, nsops, NULL); +} + +SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops, + unsigned int, nsops, const struct __kernel_timespec __user *, timeout) +{ + return ksys_semtimedop(semid, tsops, nsops, timeout); +} + +#ifdef CONFIG_COMPAT_32BIT_TIME +long compat_ksys_semtimedop(int semid, struct sembuf __user *tsems, + unsigned int nsops, + const struct old_timespec32 __user *timeout) +{ + if (timeout) { + struct timespec64 ts; + if (get_old_timespec32(&ts, timeout)) + return -EFAULT; + return do_semtimedop(semid, tsems, nsops, &ts); + } + return do_semtimedop(semid, tsems, nsops, NULL); +} + +SYSCALL_DEFINE4(semtimedop_time32, int, semid, struct sembuf __user *, tsems, + unsigned int, nsops, + const struct old_timespec32 __user *, timeout) +{ + return compat_ksys_semtimedop(semid, tsems, nsops, timeout); +} +#endif + +SYSCALL_DEFINE3(semop, int, semid, struct sembuf __user *, tsops, + unsigned int, nsops) +{ + return do_semtimedop(semid, tsops, nsops, NULL); +} + +/* If CLONE_SYSVSEM is set, establish sharing of SEM_UNDO state between + * parent and child tasks. + */ + +int copy_semundo(u64 clone_flags, struct task_struct *tsk) +{ + struct sem_undo_list *undo_list; + int error; + + if (clone_flags & CLONE_SYSVSEM) { + error = get_undo_list(&undo_list); + if (error) + return error; + refcount_inc(&undo_list->refcnt); + tsk->sysvsem.undo_list = undo_list; + } else + tsk->sysvsem.undo_list = NULL; + + return 0; +} + +/* + * add semadj values to semaphores, free undo structures. + * undo structures are not freed when semaphore arrays are destroyed + * so some of them may be out of date. + * IMPLEMENTATION NOTE: There is some confusion over whether the + * set of adjustments that needs to be done should be done in an atomic + * manner or not. That is, if we are attempting to decrement the semval + * should we queue up and wait until we can do so legally? + * The original implementation attempted to do this (queue and wait). + * The current implementation does not do so. The POSIX standard + * and SVID should be consulted to determine what behavior is mandated. + */ +void exit_sem(struct task_struct *tsk) +{ + struct sem_undo_list *ulp; + + ulp = tsk->sysvsem.undo_list; + if (!ulp) + return; + tsk->sysvsem.undo_list = NULL; + + if (!refcount_dec_and_test(&ulp->refcnt)) + return; + + for (;;) { + struct sem_array *sma; + struct sem_undo *un; + int semid, i; + DEFINE_WAKE_Q(wake_q); + + cond_resched(); + + rcu_read_lock(); + un = list_entry_rcu(ulp->list_proc.next, + struct sem_undo, list_proc); + if (&un->list_proc == &ulp->list_proc) { + /* + * We must wait for freeary() before freeing this ulp, + * in case we raced with last sem_undo. There is a small + * possibility where we exit while freeary() didn't + * finish unlocking sem_undo_list. + */ + spin_lock(&ulp->lock); + spin_unlock(&ulp->lock); + rcu_read_unlock(); + break; + } + spin_lock(&ulp->lock); + semid = un->semid; + spin_unlock(&ulp->lock); + + /* exit_sem raced with IPC_RMID, nothing to do */ + if (semid == -1) { + rcu_read_unlock(); + continue; + } + + sma = sem_obtain_object_check(tsk->nsproxy->ipc_ns, semid); + /* exit_sem raced with IPC_RMID, nothing to do */ + if (IS_ERR(sma)) { + rcu_read_unlock(); + continue; + } + + sem_lock(sma, NULL, -1); + /* exit_sem raced with IPC_RMID, nothing to do */ + if (!ipc_valid_object(&sma->sem_perm)) { + sem_unlock(sma, -1); + rcu_read_unlock(); + continue; + } + un = __lookup_undo(ulp, semid); + if (un == NULL) { + /* exit_sem raced with IPC_RMID+semget() that created + * exactly the same semid. Nothing to do. + */ + sem_unlock(sma, -1); + rcu_read_unlock(); + continue; + } + + /* remove un from the linked lists */ + ipc_assert_locked_object(&sma->sem_perm); + list_del(&un->list_id); + + spin_lock(&ulp->lock); + list_del_rcu(&un->list_proc); + spin_unlock(&ulp->lock); + + /* perform adjustments registered in un */ + for (i = 0; i < sma->sem_nsems; i++) { + struct sem *semaphore = &sma->sems[i]; + if (un->semadj[i]) { + semaphore->semval += un->semadj[i]; + /* + * Range checks of the new semaphore value, + * not defined by sus: + * - Some unices ignore the undo entirely + * (e.g. HP UX 11i 11.22, Tru64 V5.1) + * - some cap the value (e.g. FreeBSD caps + * at 0, but doesn't enforce SEMVMX) + * + * Linux caps the semaphore value, both at 0 + * and at SEMVMX. + * + * Manfred <manfred@colorfullife.com> + */ + if (semaphore->semval < 0) + semaphore->semval = 0; + if (semaphore->semval > SEMVMX) + semaphore->semval = SEMVMX; + ipc_update_pid(&semaphore->sempid, task_tgid(current)); + } + } + /* maybe some queued-up processes were waiting for this */ + do_smart_update(sma, NULL, 0, 1, &wake_q); + sem_unlock(sma, -1); + rcu_read_unlock(); + wake_up_q(&wake_q); + + kvfree_rcu(un, rcu); + } + kfree(ulp); +} + +#ifdef CONFIG_PROC_FS +static int sysvipc_sem_proc_show(struct seq_file *s, void *it) +{ + struct user_namespace *user_ns = seq_user_ns(s); + struct kern_ipc_perm *ipcp = it; + struct sem_array *sma = container_of(ipcp, struct sem_array, sem_perm); + time64_t sem_otime; + + /* + * The proc interface isn't aware of sem_lock(), it calls + * ipc_lock_object(), i.e. spin_lock(&sma->sem_perm.lock). + * (in sysvipc_find_ipc) + * In order to stay compatible with sem_lock(), we must + * enter / leave complex_mode. + */ + complexmode_enter(sma); + + sem_otime = get_semotime(sma); + + seq_printf(s, + "%10d %10d %4o %10u %5u %5u %5u %5u %10llu %10llu\n", + sma->sem_perm.key, + sma->sem_perm.id, + sma->sem_perm.mode, + sma->sem_nsems, + from_kuid_munged(user_ns, sma->sem_perm.uid), + from_kgid_munged(user_ns, sma->sem_perm.gid), + from_kuid_munged(user_ns, sma->sem_perm.cuid), + from_kgid_munged(user_ns, sma->sem_perm.cgid), + sem_otime, + sma->sem_ctime); + + complexmode_tryleave(sma); + + return 0; +} +#endif diff --git a/ipc/shm.c b/ipc/shm.c new file mode 100644 index 000000000..bb1a721a3 --- /dev/null +++ b/ipc/shm.c @@ -0,0 +1,1883 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/ipc/shm.c + * Copyright (C) 1992, 1993 Krishna Balasubramanian + * Many improvements/fixes by Bruno Haible. + * Replaced `struct shm_desc' by `struct vm_area_struct', July 1994. + * Fixed the shm swap deallocation (shm_unuse()), August 1998 Andrea Arcangeli. + * + * /proc/sysvipc/shm support (c) 1999 Dragos Acostachioaie <dragos@iname.com> + * BIGMEM support, Andrea Arcangeli <andrea@suse.de> + * SMP thread shm, Jean-Luc Boyard <jean-luc.boyard@siemens.fr> + * HIGHMEM support, Ingo Molnar <mingo@redhat.com> + * Make shmmax, shmall, shmmni sysctl'able, Christoph Rohland <cr@sap.com> + * Shared /dev/zero support, Kanoj Sarcar <kanoj@sgi.com> + * Move the mm functionality over to mm/shmem.c, Christoph Rohland <cr@sap.com> + * + * support for audit of ipc object properties and permission changes + * Dustin Kirkland <dustin.kirkland@us.ibm.com> + * + * namespaces support + * OpenVZ, SWsoft Inc. + * Pavel Emelianov <xemul@openvz.org> + * + * Better ipc lock (kern_ipc_perm.lock) handling + * Davidlohr Bueso <davidlohr.bueso@hp.com>, June 2013. + */ + +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/hugetlb.h> +#include <linux/shm.h> +#include <uapi/linux/shm.h> +#include <linux/init.h> +#include <linux/file.h> +#include <linux/mman.h> +#include <linux/shmem_fs.h> +#include <linux/security.h> +#include <linux/syscalls.h> +#include <linux/audit.h> +#include <linux/capability.h> +#include <linux/ptrace.h> +#include <linux/seq_file.h> +#include <linux/rwsem.h> +#include <linux/nsproxy.h> +#include <linux/mount.h> +#include <linux/ipc_namespace.h> +#include <linux/rhashtable.h> +#include <linux/nstree.h> + +#include <linux/uaccess.h> + +#include "util.h" + +struct shmid_kernel /* private to the kernel */ +{ + struct kern_ipc_perm shm_perm; + struct file *shm_file; + unsigned long shm_nattch; + unsigned long shm_segsz; + time64_t shm_atim; + time64_t shm_dtim; + time64_t shm_ctim; + struct pid *shm_cprid; + struct pid *shm_lprid; + struct ucounts *mlock_ucounts; + + /* + * The task created the shm object, for + * task_lock(shp->shm_creator) + */ + struct task_struct *shm_creator; + + /* + * List by creator. task_lock(->shm_creator) required for read/write. + * If list_empty(), then the creator is dead already. + */ + struct list_head shm_clist; + struct ipc_namespace *ns; +} __randomize_layout; + +/* shm_mode upper byte flags */ +#define SHM_DEST 01000 /* segment will be destroyed on last detach */ +#define SHM_LOCKED 02000 /* segment will not be swapped */ + +struct shm_file_data { + int id; + struct ipc_namespace *ns; + struct file *file; + const struct vm_operations_struct *vm_ops; +}; + +#define shm_file_data(file) (*((struct shm_file_data **)&(file)->private_data)) + +static const struct file_operations shm_file_operations; +static const struct vm_operations_struct shm_vm_ops; + +#define shm_ids(ns) ((ns)->ids[IPC_SHM_IDS]) + +#define shm_unlock(shp) \ + ipc_unlock(&(shp)->shm_perm) + +static int newseg(struct ipc_namespace *, struct ipc_params *); +static void shm_open(struct vm_area_struct *vma); +static void shm_close(struct vm_area_struct *vma); +static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp); +#ifdef CONFIG_PROC_FS +static int sysvipc_shm_proc_show(struct seq_file *s, void *it); +#endif + +void shm_init_ns(struct ipc_namespace *ns) +{ + ns->shm_ctlmax = SHMMAX; + ns->shm_ctlall = SHMALL; + ns->shm_ctlmni = SHMMNI; + ns->shm_rmid_forced = 0; + ns->shm_tot = 0; + ipc_init_ids(&shm_ids(ns)); +} + +/* + * Called with shm_ids.rwsem (writer) and the shp structure locked. + * Only shm_ids.rwsem remains locked on exit. + */ +static void do_shm_rmid(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp) +{ + struct shmid_kernel *shp; + + shp = container_of(ipcp, struct shmid_kernel, shm_perm); + WARN_ON(ns != shp->ns); + + if (shp->shm_nattch) { + shp->shm_perm.mode |= SHM_DEST; + /* Do not find it any more */ + ipc_set_key_private(&shm_ids(ns), &shp->shm_perm); + shm_unlock(shp); + } else + shm_destroy(ns, shp); +} + +#ifdef CONFIG_IPC_NS +void shm_exit_ns(struct ipc_namespace *ns) +{ + free_ipcs(ns, &shm_ids(ns), do_shm_rmid); + idr_destroy(&ns->ids[IPC_SHM_IDS].ipcs_idr); + rhashtable_destroy(&ns->ids[IPC_SHM_IDS].key_ht); +} +#endif + +static int __init ipc_ns_init(void) +{ + shm_init_ns(&init_ipc_ns); + ns_tree_add(&init_ipc_ns); + return 0; +} + +pure_initcall(ipc_ns_init); + +void __init shm_init(void) +{ + ipc_init_proc_interface("sysvipc/shm", +#if BITS_PER_LONG <= 32 + " key shmid perms size cpid lpid nattch uid gid cuid cgid atime dtime ctime rss swap\n", +#else + " key shmid perms size cpid lpid nattch uid gid cuid cgid atime dtime ctime rss swap\n", +#endif + IPC_SHM_IDS, sysvipc_shm_proc_show); +} + +static inline struct shmid_kernel *shm_obtain_object(struct ipc_namespace *ns, int id) +{ + struct kern_ipc_perm *ipcp = ipc_obtain_object_idr(&shm_ids(ns), id); + + if (IS_ERR(ipcp)) + return ERR_CAST(ipcp); + + return container_of(ipcp, struct shmid_kernel, shm_perm); +} + +static inline struct shmid_kernel *shm_obtain_object_check(struct ipc_namespace *ns, int id) +{ + struct kern_ipc_perm *ipcp = ipc_obtain_object_check(&shm_ids(ns), id); + + if (IS_ERR(ipcp)) + return ERR_CAST(ipcp); + + return container_of(ipcp, struct shmid_kernel, shm_perm); +} + +/* + * shm_lock_(check_) routines are called in the paths where the rwsem + * is not necessarily held. + */ +static inline struct shmid_kernel *shm_lock(struct ipc_namespace *ns, int id) +{ + struct kern_ipc_perm *ipcp; + + rcu_read_lock(); + ipcp = ipc_obtain_object_idr(&shm_ids(ns), id); + if (IS_ERR(ipcp)) + goto err; + + ipc_lock_object(ipcp); + /* + * ipc_rmid() may have already freed the ID while ipc_lock_object() + * was spinning: here verify that the structure is still valid. + * Upon races with RMID, return -EIDRM, thus indicating that + * the ID points to a removed identifier. + */ + if (ipc_valid_object(ipcp)) { + /* return a locked ipc object upon success */ + return container_of(ipcp, struct shmid_kernel, shm_perm); + } + + ipc_unlock_object(ipcp); + ipcp = ERR_PTR(-EIDRM); +err: + rcu_read_unlock(); + /* + * Callers of shm_lock() must validate the status of the returned ipc + * object pointer and error out as appropriate. + */ + return ERR_CAST(ipcp); +} + +static inline void shm_lock_by_ptr(struct shmid_kernel *ipcp) +{ + rcu_read_lock(); + ipc_lock_object(&ipcp->shm_perm); +} + +static void shm_rcu_free(struct rcu_head *head) +{ + struct kern_ipc_perm *ptr = container_of(head, struct kern_ipc_perm, + rcu); + struct shmid_kernel *shp = container_of(ptr, struct shmid_kernel, + shm_perm); + security_shm_free(&shp->shm_perm); + kfree(shp); +} + +/* + * It has to be called with shp locked. + * It must be called before ipc_rmid() + */ +static inline void shm_clist_rm(struct shmid_kernel *shp) +{ + struct task_struct *creator; + + /* ensure that shm_creator does not disappear */ + rcu_read_lock(); + + /* + * A concurrent exit_shm may do a list_del_init() as well. + * Just do nothing if exit_shm already did the work + */ + if (!list_empty(&shp->shm_clist)) { + /* + * shp->shm_creator is guaranteed to be valid *only* + * if shp->shm_clist is not empty. + */ + creator = shp->shm_creator; + + task_lock(creator); + /* + * list_del_init() is a nop if the entry was already removed + * from the list. + */ + list_del_init(&shp->shm_clist); + task_unlock(creator); + } + rcu_read_unlock(); +} + +static inline void shm_rmid(struct shmid_kernel *s) +{ + shm_clist_rm(s); + ipc_rmid(&shm_ids(s->ns), &s->shm_perm); +} + + +static int __shm_open(struct shm_file_data *sfd) +{ + struct shmid_kernel *shp; + + shp = shm_lock(sfd->ns, sfd->id); + + if (IS_ERR(shp)) + return PTR_ERR(shp); + + if (shp->shm_file != sfd->file) { + /* ID was reused */ + shm_unlock(shp); + return -EINVAL; + } + + shp->shm_atim = ktime_get_real_seconds(); + ipc_update_pid(&shp->shm_lprid, task_tgid(current)); + shp->shm_nattch++; + shm_unlock(shp); + return 0; +} + +/* This is called by fork, once for every shm attach. */ +static void shm_open(struct vm_area_struct *vma) +{ + struct file *file = vma->vm_file; + struct shm_file_data *sfd = shm_file_data(file); + int err; + + /* Always call underlying open if present */ + if (sfd->vm_ops->open) + sfd->vm_ops->open(vma); + + err = __shm_open(sfd); + /* + * We raced in the idr lookup or with shm_destroy(). + * Either way, the ID is busted. + */ + WARN_ON_ONCE(err); +} + +/* + * shm_destroy - free the struct shmid_kernel + * + * @ns: namespace + * @shp: struct to free + * + * It has to be called with shp and shm_ids.rwsem (writer) locked, + * but returns with shp unlocked and freed. + */ +static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp) +{ + struct file *shm_file; + + shm_file = shp->shm_file; + shp->shm_file = NULL; + ns->shm_tot -= (shp->shm_segsz + PAGE_SIZE - 1) >> PAGE_SHIFT; + shm_rmid(shp); + shm_unlock(shp); + if (!is_file_hugepages(shm_file)) + shmem_lock(shm_file, 0, shp->mlock_ucounts); + fput(shm_file); + ipc_update_pid(&shp->shm_cprid, NULL); + ipc_update_pid(&shp->shm_lprid, NULL); + ipc_rcu_putref(&shp->shm_perm, shm_rcu_free); +} + +/* + * shm_may_destroy - identifies whether shm segment should be destroyed now + * + * Returns true if and only if there are no active users of the segment and + * one of the following is true: + * + * 1) shmctl(id, IPC_RMID, NULL) was called for this shp + * + * 2) sysctl kernel.shm_rmid_forced is set to 1. + */ +static bool shm_may_destroy(struct shmid_kernel *shp) +{ + return (shp->shm_nattch == 0) && + (shp->ns->shm_rmid_forced || + (shp->shm_perm.mode & SHM_DEST)); +} + +/* + * remove the attach descriptor vma. + * free memory for segment if it is marked destroyed. + * The descriptor has already been removed from the current->mm->mmap list + * and will later be kfree()d. + */ +static void __shm_close(struct shm_file_data *sfd) +{ + struct shmid_kernel *shp; + struct ipc_namespace *ns = sfd->ns; + + down_write(&shm_ids(ns).rwsem); + /* remove from the list of attaches of the shm segment */ + shp = shm_lock(ns, sfd->id); + + /* + * We raced in the idr lookup or with shm_destroy(). + * Either way, the ID is busted. + */ + if (WARN_ON_ONCE(IS_ERR(shp))) + goto done; /* no-op */ + + ipc_update_pid(&shp->shm_lprid, task_tgid(current)); + shp->shm_dtim = ktime_get_real_seconds(); + shp->shm_nattch--; + if (shm_may_destroy(shp)) + shm_destroy(ns, shp); + else + shm_unlock(shp); +done: + up_write(&shm_ids(ns).rwsem); +} + +static void shm_close(struct vm_area_struct *vma) +{ + struct file *file = vma->vm_file; + struct shm_file_data *sfd = shm_file_data(file); + + /* Always call underlying close if present */ + if (sfd->vm_ops->close) + sfd->vm_ops->close(vma); + + __shm_close(sfd); +} + +/* Called with ns->shm_ids(ns).rwsem locked */ +static int shm_try_destroy_orphaned(int id, void *p, void *data) +{ + struct ipc_namespace *ns = data; + struct kern_ipc_perm *ipcp = p; + struct shmid_kernel *shp = container_of(ipcp, struct shmid_kernel, shm_perm); + + /* + * We want to destroy segments without users and with already + * exit'ed originating process. + * + * shm_nattch can be changed under shm_perm.lock without holding the + * rwsem, so take the object lock before checking shm_may_destroy(). + */ + if (!list_empty(&shp->shm_clist)) + return 0; + + shm_lock_by_ptr(shp); + if (shm_may_destroy(shp)) + shm_destroy(ns, shp); + else + shm_unlock(shp); + return 0; +} + +void shm_destroy_orphaned(struct ipc_namespace *ns) +{ + down_write(&shm_ids(ns).rwsem); + if (shm_ids(ns).in_use) { + rcu_read_lock(); + idr_for_each(&shm_ids(ns).ipcs_idr, &shm_try_destroy_orphaned, ns); + rcu_read_unlock(); + } + up_write(&shm_ids(ns).rwsem); +} + +/* Locking assumes this will only be called with task == current */ +void exit_shm(struct task_struct *task) +{ + for (;;) { + struct shmid_kernel *shp; + struct ipc_namespace *ns; + + task_lock(task); + + if (list_empty(&task->sysvshm.shm_clist)) { + task_unlock(task); + break; + } + + shp = list_first_entry(&task->sysvshm.shm_clist, struct shmid_kernel, + shm_clist); + + /* + * 1) Get pointer to the ipc namespace. It is worth to say + * that this pointer is guaranteed to be valid because + * shp lifetime is always shorter than namespace lifetime + * in which shp lives. + * We taken task_lock it means that shp won't be freed. + */ + ns = shp->ns; + + /* + * 2) If kernel.shm_rmid_forced is not set then only keep track of + * which shmids are orphaned, so that a later set of the sysctl + * can clean them up. + */ + if (!ns->shm_rmid_forced) + goto unlink_continue; + + /* + * 3) get a reference to the namespace. + * The refcount could be already 0. If it is 0, then + * the shm objects will be free by free_ipc_work(). + */ + ns = get_ipc_ns_not_zero(ns); + if (!ns) { +unlink_continue: + list_del_init(&shp->shm_clist); + task_unlock(task); + continue; + } + + /* + * 4) get a reference to shp. + * This cannot fail: shm_clist_rm() is called before + * ipc_rmid(), thus the refcount cannot be 0. + */ + WARN_ON(!ipc_rcu_getref(&shp->shm_perm)); + + /* + * 5) unlink the shm segment from the list of segments + * created by current. + * This must be done last. After unlinking, + * only the refcounts obtained above prevent IPC_RMID + * from destroying the segment or the namespace. + */ + list_del_init(&shp->shm_clist); + + task_unlock(task); + + /* + * 6) we have all references + * Thus lock & if needed destroy shp. + */ + down_write(&shm_ids(ns).rwsem); + shm_lock_by_ptr(shp); + /* + * rcu_read_lock was implicitly taken in shm_lock_by_ptr, it's + * safe to call ipc_rcu_putref here + */ + ipc_rcu_putref(&shp->shm_perm, shm_rcu_free); + + if (ipc_valid_object(&shp->shm_perm)) { + if (shm_may_destroy(shp)) + shm_destroy(ns, shp); + else + shm_unlock(shp); + } else { + /* + * Someone else deleted the shp from namespace + * idr/kht while we have waited. + * Just unlock and continue. + */ + shm_unlock(shp); + } + + up_write(&shm_ids(ns).rwsem); + put_ipc_ns(ns); /* paired with get_ipc_ns_not_zero */ + } +} + +static vm_fault_t shm_fault(struct vm_fault *vmf) +{ + struct file *file = vmf->vma->vm_file; + struct shm_file_data *sfd = shm_file_data(file); + + return sfd->vm_ops->fault(vmf); +} + +static int shm_may_split(struct vm_area_struct *vma, unsigned long addr) +{ + struct file *file = vma->vm_file; + struct shm_file_data *sfd = shm_file_data(file); + + if (sfd->vm_ops->may_split) + return sfd->vm_ops->may_split(vma, addr); + + return 0; +} + +static unsigned long shm_pagesize(struct vm_area_struct *vma) +{ + struct file *file = vma->vm_file; + struct shm_file_data *sfd = shm_file_data(file); + + if (sfd->vm_ops->pagesize) + return sfd->vm_ops->pagesize(vma); + + return PAGE_SIZE; +} + +#ifdef CONFIG_NUMA +static int shm_set_policy(struct vm_area_struct *vma, struct mempolicy *mpol) +{ + struct shm_file_data *sfd = shm_file_data(vma->vm_file); + int err = 0; + + if (sfd->vm_ops->set_policy) + err = sfd->vm_ops->set_policy(vma, mpol); + return err; +} + +static struct mempolicy *shm_get_policy(struct vm_area_struct *vma, + unsigned long addr, pgoff_t *ilx) +{ + struct shm_file_data *sfd = shm_file_data(vma->vm_file); + struct mempolicy *mpol = vma->vm_policy; + + if (sfd->vm_ops->get_policy) + mpol = sfd->vm_ops->get_policy(vma, addr, ilx); + return mpol; +} +#endif + +static int shm_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct shm_file_data *sfd = shm_file_data(file); + int ret; + + /* + * In case of remap_file_pages() emulation, the file can represent an + * IPC ID that was removed, and possibly even reused by another shm + * segment already. Propagate this case as an error to caller. + */ + ret = __shm_open(sfd); + if (ret) + return ret; + + ret = vfs_mmap(sfd->file, vma); + if (ret) { + __shm_close(sfd); + return ret; + } + sfd->vm_ops = vma->vm_ops; +#ifdef CONFIG_MMU + WARN_ON(!sfd->vm_ops->fault); +#endif + vma->vm_ops = &shm_vm_ops; + return 0; +} + +static int shm_release(struct inode *ino, struct file *file) +{ + struct shm_file_data *sfd = shm_file_data(file); + + put_ipc_ns(sfd->ns); + fput(sfd->file); + shm_file_data(file) = NULL; + kfree(sfd); + return 0; +} + +static int shm_fsync(struct file *file, loff_t start, loff_t end, int datasync) +{ + struct shm_file_data *sfd = shm_file_data(file); + + if (!sfd->file->f_op->fsync) + return -EINVAL; + return sfd->file->f_op->fsync(sfd->file, start, end, datasync); +} + +static long shm_fallocate(struct file *file, int mode, loff_t offset, + loff_t len) +{ + struct shm_file_data *sfd = shm_file_data(file); + + if (!sfd->file->f_op->fallocate) + return -EOPNOTSUPP; + return sfd->file->f_op->fallocate(file, mode, offset, len); +} + +static unsigned long shm_get_unmapped_area(struct file *file, + unsigned long addr, unsigned long len, unsigned long pgoff, + unsigned long flags) +{ + struct shm_file_data *sfd = shm_file_data(file); + + return sfd->file->f_op->get_unmapped_area(sfd->file, addr, len, + pgoff, flags); +} + +static const struct file_operations shm_file_operations = { + .mmap = shm_mmap, + .fsync = shm_fsync, + .release = shm_release, + .get_unmapped_area = shm_get_unmapped_area, + .llseek = noop_llseek, + .fallocate = shm_fallocate, +}; + +/* + * shm_file_operations_huge is now identical to shm_file_operations + * except for fop_flags + */ +static const struct file_operations shm_file_operations_huge = { + .mmap = shm_mmap, + .fsync = shm_fsync, + .release = shm_release, + .get_unmapped_area = shm_get_unmapped_area, + .llseek = noop_llseek, + .fallocate = shm_fallocate, + .fop_flags = FOP_HUGE_PAGES, +}; + +static const struct vm_operations_struct shm_vm_ops = { + .open = shm_open, /* callback for a new vm-area open */ + .close = shm_close, /* callback for when the vm-area is released */ + .fault = shm_fault, + .may_split = shm_may_split, + .pagesize = shm_pagesize, +#if defined(CONFIG_NUMA) + .set_policy = shm_set_policy, + .get_policy = shm_get_policy, +#endif +}; + +/** + * newseg - Create a new shared memory segment + * @ns: namespace + * @params: ptr to the structure that contains key, size and shmflg + * + * Called with shm_ids.rwsem held as a writer. + */ +static int newseg(struct ipc_namespace *ns, struct ipc_params *params) +{ + key_t key = params->key; + int shmflg = params->flg; + size_t size = params->u.size; + int error; + struct shmid_kernel *shp; + size_t numpages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; + const bool has_no_reserve = shmflg & SHM_NORESERVE; + vma_flags_t acctflag = EMPTY_VMA_FLAGS; + struct file *file; + char name[13]; + + if (size < SHMMIN || size > ns->shm_ctlmax) + return -EINVAL; + + if (numpages << PAGE_SHIFT < size) + return -ENOSPC; + + if (ns->shm_tot + numpages < ns->shm_tot || + ns->shm_tot + numpages > ns->shm_ctlall) + return -ENOSPC; + + shp = kmalloc_obj(*shp, GFP_KERNEL_ACCOUNT); + if (unlikely(!shp)) + return -ENOMEM; + + shp->shm_perm.key = key; + shp->shm_perm.mode = (shmflg & S_IRWXUGO); + shp->mlock_ucounts = NULL; + + shp->shm_perm.security = NULL; + error = security_shm_alloc(&shp->shm_perm); + if (error) { + kfree(shp); + return error; + } + + sprintf(name, "SYSV%08x", key); + if (shmflg & SHM_HUGETLB) { + struct hstate *hs; + size_t hugesize; + + hs = hstate_sizelog((shmflg >> SHM_HUGE_SHIFT) & SHM_HUGE_MASK); + if (!hs) { + error = -EINVAL; + goto no_file; + } + hugesize = ALIGN(size, huge_page_size(hs)); + + /* hugetlb_file_setup applies strict accounting */ + if (has_no_reserve) + vma_flags_set(&acctflag, VMA_NORESERVE_BIT); + file = hugetlb_file_setup(name, hugesize, acctflag, + HUGETLB_SHMFS_INODE, (shmflg >> SHM_HUGE_SHIFT) & SHM_HUGE_MASK); + } else { + /* + * Do not allow no accounting for OVERCOMMIT_NEVER, even + * if it's asked for. + */ + if (has_no_reserve && sysctl_overcommit_memory != OVERCOMMIT_NEVER) + vma_flags_set(&acctflag, VMA_NORESERVE_BIT); + file = shmem_kernel_file_setup(name, size, acctflag); + } + error = PTR_ERR(file); + if (IS_ERR(file)) + goto no_file; + + shp->shm_cprid = get_pid(task_tgid(current)); + shp->shm_lprid = NULL; + shp->shm_atim = shp->shm_dtim = 0; + shp->shm_ctim = ktime_get_real_seconds(); + shp->shm_segsz = size; + shp->shm_nattch = 0; + shp->shm_file = file; + shp->shm_creator = current; + + /* ipc_addid() locks shp upon success. */ + error = ipc_addid(&shm_ids(ns), &shp->shm_perm, ns->shm_ctlmni); + if (error < 0) + goto no_id; + + shp->ns = ns; + + task_lock(current); + list_add(&shp->shm_clist, ¤t->sysvshm.shm_clist); + task_unlock(current); + + /* + * shmid gets reported as "inode#" in /proc/pid/maps. + * proc-ps tools use this. Changing this will break them. + */ + file_inode(file)->i_ino = shp->shm_perm.id; + + ns->shm_tot += numpages; + error = shp->shm_perm.id; + + ipc_unlock_object(&shp->shm_perm); + rcu_read_unlock(); + return error; + +no_id: + ipc_update_pid(&shp->shm_cprid, NULL); + ipc_update_pid(&shp->shm_lprid, NULL); + fput(file); + ipc_rcu_putref(&shp->shm_perm, shm_rcu_free); + return error; +no_file: + call_rcu(&shp->shm_perm.rcu, shm_rcu_free); + return error; +} + +/* + * Called with shm_ids.rwsem and ipcp locked. + */ +static int shm_more_checks(struct kern_ipc_perm *ipcp, struct ipc_params *params) +{ + struct shmid_kernel *shp; + + shp = container_of(ipcp, struct shmid_kernel, shm_perm); + if (shp->shm_segsz < params->u.size) + return -EINVAL; + + return 0; +} + +long ksys_shmget(key_t key, size_t size, int shmflg) +{ + struct ipc_namespace *ns; + static const struct ipc_ops shm_ops = { + .getnew = newseg, + .associate = security_shm_associate, + .more_checks = shm_more_checks, + }; + struct ipc_params shm_params; + + ns = current->nsproxy->ipc_ns; + + shm_params.key = key; + shm_params.flg = shmflg; + shm_params.u.size = size; + + return ipcget(ns, &shm_ids(ns), &shm_ops, &shm_params); +} + +SYSCALL_DEFINE3(shmget, key_t, key, size_t, size, int, shmflg) +{ + return ksys_shmget(key, size, shmflg); +} + +static inline unsigned long copy_shmid_to_user(void __user *buf, struct shmid64_ds *in, int version) +{ + switch (version) { + case IPC_64: + return copy_to_user(buf, in, sizeof(*in)); + case IPC_OLD: + { + struct shmid_ds out; + + memset(&out, 0, sizeof(out)); + ipc64_perm_to_ipc_perm(&in->shm_perm, &out.shm_perm); + out.shm_segsz = in->shm_segsz; + out.shm_atime = in->shm_atime; + out.shm_dtime = in->shm_dtime; + out.shm_ctime = in->shm_ctime; + out.shm_cpid = in->shm_cpid; + out.shm_lpid = in->shm_lpid; + out.shm_nattch = in->shm_nattch; + + return copy_to_user(buf, &out, sizeof(out)); + } + default: + return -EINVAL; + } +} + +static inline unsigned long +copy_shmid_from_user(struct shmid64_ds *out, void __user *buf, int version) +{ + switch (version) { + case IPC_64: + if (copy_from_user(out, buf, sizeof(*out))) + return -EFAULT; + return 0; + case IPC_OLD: + { + struct shmid_ds tbuf_old; + + if (copy_from_user(&tbuf_old, buf, sizeof(tbuf_old))) + return -EFAULT; + + out->shm_perm.uid = tbuf_old.shm_perm.uid; + out->shm_perm.gid = tbuf_old.shm_perm.gid; + out->shm_perm.mode = tbuf_old.shm_perm.mode; + + return 0; + } + default: + return -EINVAL; + } +} + +static inline unsigned long copy_shminfo_to_user(void __user *buf, struct shminfo64 *in, int version) +{ + switch (version) { + case IPC_64: + return copy_to_user(buf, in, sizeof(*in)); + case IPC_OLD: + { + struct shminfo out; + + if (in->shmmax > INT_MAX) + out.shmmax = INT_MAX; + else + out.shmmax = (int)in->shmmax; + + out.shmmin = in->shmmin; + out.shmmni = in->shmmni; + out.shmseg = in->shmseg; + out.shmall = in->shmall; + + return copy_to_user(buf, &out, sizeof(out)); + } + default: + return -EINVAL; + } +} + +/* + * Calculate and add used RSS and swap pages of a shm. + * Called with shm_ids.rwsem held as a reader + */ +static void shm_add_rss_swap(struct shmid_kernel *shp, + unsigned long *rss_add, unsigned long *swp_add) +{ + struct inode *inode; + + inode = file_inode(shp->shm_file); + + if (is_file_hugepages(shp->shm_file)) { + struct address_space *mapping = inode->i_mapping; + struct hstate *h = hstate_file(shp->shm_file); + *rss_add += pages_per_huge_page(h) * mapping->nrpages; + } else { +#ifdef CONFIG_SHMEM + struct shmem_inode_info *info = SHMEM_I(inode); + + spin_lock_irq(&info->lock); + *rss_add += inode->i_mapping->nrpages; + *swp_add += info->swapped; + spin_unlock_irq(&info->lock); +#else + *rss_add += inode->i_mapping->nrpages; +#endif + } +} + +/* + * Called with shm_ids.rwsem held as a reader + */ +static void shm_get_stat(struct ipc_namespace *ns, unsigned long *rss, + unsigned long *swp) +{ + int next_id; + int total, in_use; + + *rss = 0; + *swp = 0; + + in_use = shm_ids(ns).in_use; + + for (total = 0, next_id = 0; total < in_use; next_id++) { + struct kern_ipc_perm *ipc; + struct shmid_kernel *shp; + + ipc = idr_find(&shm_ids(ns).ipcs_idr, next_id); + if (ipc == NULL) + continue; + shp = container_of(ipc, struct shmid_kernel, shm_perm); + + shm_add_rss_swap(shp, rss, swp); + + total++; + } +} + +/* + * This function handles some shmctl commands which require the rwsem + * to be held in write mode. + * NOTE: no locks must be held, the rwsem is taken inside this function. + */ +static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd, + struct shmid64_ds *shmid64) +{ + struct kern_ipc_perm *ipcp; + struct shmid_kernel *shp; + int err; + + down_write(&shm_ids(ns).rwsem); + rcu_read_lock(); + + ipcp = ipcctl_obtain_check(ns, &shm_ids(ns), shmid, cmd, + &shmid64->shm_perm, 0); + if (IS_ERR(ipcp)) { + err = PTR_ERR(ipcp); + goto out_unlock1; + } + + shp = container_of(ipcp, struct shmid_kernel, shm_perm); + + err = security_shm_shmctl(&shp->shm_perm, cmd); + if (err) + goto out_unlock1; + + switch (cmd) { + case IPC_RMID: + ipc_lock_object(&shp->shm_perm); + /* do_shm_rmid unlocks the ipc object and rcu */ + do_shm_rmid(ns, ipcp); + goto out_up; + case IPC_SET: + ipc_lock_object(&shp->shm_perm); + err = ipc_update_perm(&shmid64->shm_perm, ipcp); + if (err) + goto out_unlock0; + shp->shm_ctim = ktime_get_real_seconds(); + break; + default: + err = -EINVAL; + goto out_unlock1; + } + +out_unlock0: + ipc_unlock_object(&shp->shm_perm); +out_unlock1: + rcu_read_unlock(); +out_up: + up_write(&shm_ids(ns).rwsem); + return err; +} + +static int shmctl_ipc_info(struct ipc_namespace *ns, + struct shminfo64 *shminfo) +{ + int err = security_shm_shmctl(NULL, IPC_INFO); + if (!err) { + memset(shminfo, 0, sizeof(*shminfo)); + shminfo->shmmni = shminfo->shmseg = ns->shm_ctlmni; + shminfo->shmmax = ns->shm_ctlmax; + shminfo->shmall = ns->shm_ctlall; + shminfo->shmmin = SHMMIN; + down_read(&shm_ids(ns).rwsem); + err = ipc_get_maxidx(&shm_ids(ns)); + up_read(&shm_ids(ns).rwsem); + if (err < 0) + err = 0; + } + return err; +} + +static int shmctl_shm_info(struct ipc_namespace *ns, + struct shm_info *shm_info) +{ + int err = security_shm_shmctl(NULL, SHM_INFO); + if (!err) { + memset(shm_info, 0, sizeof(*shm_info)); + down_read(&shm_ids(ns).rwsem); + shm_info->used_ids = shm_ids(ns).in_use; + shm_get_stat(ns, &shm_info->shm_rss, &shm_info->shm_swp); + shm_info->shm_tot = ns->shm_tot; + shm_info->swap_attempts = 0; + shm_info->swap_successes = 0; + err = ipc_get_maxidx(&shm_ids(ns)); + up_read(&shm_ids(ns).rwsem); + if (err < 0) + err = 0; + } + return err; +} + +static int shmctl_stat(struct ipc_namespace *ns, int shmid, + int cmd, struct shmid64_ds *tbuf) +{ + struct shmid_kernel *shp; + int err; + + memset(tbuf, 0, sizeof(*tbuf)); + + rcu_read_lock(); + if (cmd == SHM_STAT || cmd == SHM_STAT_ANY) { + shp = shm_obtain_object(ns, shmid); + if (IS_ERR(shp)) { + err = PTR_ERR(shp); + goto out_unlock; + } + } else { /* IPC_STAT */ + shp = shm_obtain_object_check(ns, shmid); + if (IS_ERR(shp)) { + err = PTR_ERR(shp); + goto out_unlock; + } + } + + /* + * Semantically SHM_STAT_ANY ought to be identical to + * that functionality provided by the /proc/sysvipc/ + * interface. As such, only audit these calls and + * do not do traditional S_IRUGO permission checks on + * the ipc object. + */ + if (cmd == SHM_STAT_ANY) + audit_ipc_obj(&shp->shm_perm); + else { + err = -EACCES; + if (ipcperms(ns, &shp->shm_perm, S_IRUGO)) + goto out_unlock; + } + + err = security_shm_shmctl(&shp->shm_perm, cmd); + if (err) + goto out_unlock; + + ipc_lock_object(&shp->shm_perm); + + if (!ipc_valid_object(&shp->shm_perm)) { + ipc_unlock_object(&shp->shm_perm); + err = -EIDRM; + goto out_unlock; + } + + kernel_to_ipc64_perm(&shp->shm_perm, &tbuf->shm_perm); + tbuf->shm_segsz = shp->shm_segsz; + tbuf->shm_atime = shp->shm_atim; + tbuf->shm_dtime = shp->shm_dtim; + tbuf->shm_ctime = shp->shm_ctim; +#ifndef CONFIG_64BIT + tbuf->shm_atime_high = shp->shm_atim >> 32; + tbuf->shm_dtime_high = shp->shm_dtim >> 32; + tbuf->shm_ctime_high = shp->shm_ctim >> 32; +#endif + tbuf->shm_cpid = pid_vnr(shp->shm_cprid); + tbuf->shm_lpid = pid_vnr(shp->shm_lprid); + tbuf->shm_nattch = shp->shm_nattch; + + if (cmd == IPC_STAT) { + /* + * As defined in SUS: + * Return 0 on success + */ + err = 0; + } else { + /* + * SHM_STAT and SHM_STAT_ANY (both Linux specific) + * Return the full id, including the sequence number + */ + err = shp->shm_perm.id; + } + + ipc_unlock_object(&shp->shm_perm); +out_unlock: + rcu_read_unlock(); + return err; +} + +static int shmctl_do_lock(struct ipc_namespace *ns, int shmid, int cmd) +{ + struct shmid_kernel *shp; + struct file *shm_file; + int err; + + rcu_read_lock(); + shp = shm_obtain_object_check(ns, shmid); + if (IS_ERR(shp)) { + err = PTR_ERR(shp); + goto out_unlock1; + } + + audit_ipc_obj(&(shp->shm_perm)); + err = security_shm_shmctl(&shp->shm_perm, cmd); + if (err) + goto out_unlock1; + + ipc_lock_object(&shp->shm_perm); + + /* check if shm_destroy() is tearing down shp */ + if (!ipc_valid_object(&shp->shm_perm)) { + err = -EIDRM; + goto out_unlock0; + } + + if (!ns_capable(ns->user_ns, CAP_IPC_LOCK)) { + kuid_t euid = current_euid(); + + if (!uid_eq(euid, shp->shm_perm.uid) && + !uid_eq(euid, shp->shm_perm.cuid)) { + err = -EPERM; + goto out_unlock0; + } + if (cmd == SHM_LOCK && !rlimit(RLIMIT_MEMLOCK)) { + err = -EPERM; + goto out_unlock0; + } + } + + shm_file = shp->shm_file; + if (is_file_hugepages(shm_file)) + goto out_unlock0; + + if (cmd == SHM_LOCK) { + struct ucounts *ucounts = current_ucounts(); + + err = shmem_lock(shm_file, 1, ucounts); + if (!err && !(shp->shm_perm.mode & SHM_LOCKED)) { + shp->shm_perm.mode |= SHM_LOCKED; + shp->mlock_ucounts = ucounts; + } + goto out_unlock0; + } + + /* SHM_UNLOCK */ + if (!(shp->shm_perm.mode & SHM_LOCKED)) + goto out_unlock0; + shmem_lock(shm_file, 0, shp->mlock_ucounts); + shp->shm_perm.mode &= ~SHM_LOCKED; + shp->mlock_ucounts = NULL; + get_file(shm_file); + ipc_unlock_object(&shp->shm_perm); + rcu_read_unlock(); + shmem_unlock_mapping(shm_file->f_mapping); + + fput(shm_file); + return err; + +out_unlock0: + ipc_unlock_object(&shp->shm_perm); +out_unlock1: + rcu_read_unlock(); + return err; +} + +static long ksys_shmctl(int shmid, int cmd, struct shmid_ds __user *buf, int version) +{ + int err; + struct ipc_namespace *ns; + struct shmid64_ds sem64; + + if (cmd < 0 || shmid < 0) + return -EINVAL; + + ns = current->nsproxy->ipc_ns; + + switch (cmd) { + case IPC_INFO: { + struct shminfo64 shminfo; + err = shmctl_ipc_info(ns, &shminfo); + if (err < 0) + return err; + if (copy_shminfo_to_user(buf, &shminfo, version)) + err = -EFAULT; + return err; + } + case SHM_INFO: { + struct shm_info shm_info; + err = shmctl_shm_info(ns, &shm_info); + if (err < 0) + return err; + if (copy_to_user(buf, &shm_info, sizeof(shm_info))) + err = -EFAULT; + return err; + } + case SHM_STAT: + case SHM_STAT_ANY: + case IPC_STAT: { + err = shmctl_stat(ns, shmid, cmd, &sem64); + if (err < 0) + return err; + if (copy_shmid_to_user(buf, &sem64, version)) + err = -EFAULT; + return err; + } + case IPC_SET: + if (copy_shmid_from_user(&sem64, buf, version)) + return -EFAULT; + fallthrough; + case IPC_RMID: + return shmctl_down(ns, shmid, cmd, &sem64); + case SHM_LOCK: + case SHM_UNLOCK: + return shmctl_do_lock(ns, shmid, cmd); + default: + return -EINVAL; + } +} + +SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf) +{ + return ksys_shmctl(shmid, cmd, buf, IPC_64); +} + +#ifdef CONFIG_ARCH_WANT_IPC_PARSE_VERSION +long ksys_old_shmctl(int shmid, int cmd, struct shmid_ds __user *buf) +{ + int version = ipc_parse_version(&cmd); + + return ksys_shmctl(shmid, cmd, buf, version); +} + +SYSCALL_DEFINE3(old_shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf) +{ + return ksys_old_shmctl(shmid, cmd, buf); +} +#endif + +#ifdef CONFIG_COMPAT + +struct compat_shmid_ds { + struct compat_ipc_perm shm_perm; + int shm_segsz; + old_time32_t shm_atime; + old_time32_t shm_dtime; + old_time32_t shm_ctime; + compat_ipc_pid_t shm_cpid; + compat_ipc_pid_t shm_lpid; + unsigned short shm_nattch; + unsigned short shm_unused; + compat_uptr_t shm_unused2; + compat_uptr_t shm_unused3; +}; + +struct compat_shminfo64 { + compat_ulong_t shmmax; + compat_ulong_t shmmin; + compat_ulong_t shmmni; + compat_ulong_t shmseg; + compat_ulong_t shmall; + compat_ulong_t __unused1; + compat_ulong_t __unused2; + compat_ulong_t __unused3; + compat_ulong_t __unused4; +}; + +struct compat_shm_info { + compat_int_t used_ids; + compat_ulong_t shm_tot, shm_rss, shm_swp; + compat_ulong_t swap_attempts, swap_successes; +}; + +static int copy_compat_shminfo_to_user(void __user *buf, struct shminfo64 *in, + int version) +{ + if (in->shmmax > INT_MAX) + in->shmmax = INT_MAX; + if (version == IPC_64) { + struct compat_shminfo64 info; + memset(&info, 0, sizeof(info)); + info.shmmax = in->shmmax; + info.shmmin = in->shmmin; + info.shmmni = in->shmmni; + info.shmseg = in->shmseg; + info.shmall = in->shmall; + return copy_to_user(buf, &info, sizeof(info)); + } else { + struct shminfo info; + memset(&info, 0, sizeof(info)); + info.shmmax = in->shmmax; + info.shmmin = in->shmmin; + info.shmmni = in->shmmni; + info.shmseg = in->shmseg; + info.shmall = in->shmall; + return copy_to_user(buf, &info, sizeof(info)); + } +} + +static int put_compat_shm_info(struct shm_info *ip, + struct compat_shm_info __user *uip) +{ + struct compat_shm_info info; + + memset(&info, 0, sizeof(info)); + info.used_ids = ip->used_ids; + info.shm_tot = ip->shm_tot; + info.shm_rss = ip->shm_rss; + info.shm_swp = ip->shm_swp; + info.swap_attempts = ip->swap_attempts; + info.swap_successes = ip->swap_successes; + return copy_to_user(uip, &info, sizeof(info)); +} + +static int copy_compat_shmid_to_user(void __user *buf, struct shmid64_ds *in, + int version) +{ + if (version == IPC_64) { + struct compat_shmid64_ds v; + memset(&v, 0, sizeof(v)); + to_compat_ipc64_perm(&v.shm_perm, &in->shm_perm); + v.shm_atime = lower_32_bits(in->shm_atime); + v.shm_atime_high = upper_32_bits(in->shm_atime); + v.shm_dtime = lower_32_bits(in->shm_dtime); + v.shm_dtime_high = upper_32_bits(in->shm_dtime); + v.shm_ctime = lower_32_bits(in->shm_ctime); + v.shm_ctime_high = upper_32_bits(in->shm_ctime); + v.shm_segsz = in->shm_segsz; + v.shm_nattch = in->shm_nattch; + v.shm_cpid = in->shm_cpid; + v.shm_lpid = in->shm_lpid; + return copy_to_user(buf, &v, sizeof(v)); + } else { + struct compat_shmid_ds v; + memset(&v, 0, sizeof(v)); + to_compat_ipc_perm(&v.shm_perm, &in->shm_perm); + v.shm_perm.key = in->shm_perm.key; + v.shm_atime = in->shm_atime; + v.shm_dtime = in->shm_dtime; + v.shm_ctime = in->shm_ctime; + v.shm_segsz = in->shm_segsz; + v.shm_nattch = in->shm_nattch; + v.shm_cpid = in->shm_cpid; + v.shm_lpid = in->shm_lpid; + return copy_to_user(buf, &v, sizeof(v)); + } +} + +static int copy_compat_shmid_from_user(struct shmid64_ds *out, void __user *buf, + int version) +{ + memset(out, 0, sizeof(*out)); + if (version == IPC_64) { + struct compat_shmid64_ds __user *p = buf; + return get_compat_ipc64_perm(&out->shm_perm, &p->shm_perm); + } else { + struct compat_shmid_ds __user *p = buf; + return get_compat_ipc_perm(&out->shm_perm, &p->shm_perm); + } +} + +static long compat_ksys_shmctl(int shmid, int cmd, void __user *uptr, int version) +{ + struct ipc_namespace *ns; + struct shmid64_ds sem64; + int err; + + ns = current->nsproxy->ipc_ns; + + if (cmd < 0 || shmid < 0) + return -EINVAL; + + switch (cmd) { + case IPC_INFO: { + struct shminfo64 shminfo; + err = shmctl_ipc_info(ns, &shminfo); + if (err < 0) + return err; + if (copy_compat_shminfo_to_user(uptr, &shminfo, version)) + err = -EFAULT; + return err; + } + case SHM_INFO: { + struct shm_info shm_info; + err = shmctl_shm_info(ns, &shm_info); + if (err < 0) + return err; + if (put_compat_shm_info(&shm_info, uptr)) + err = -EFAULT; + return err; + } + case IPC_STAT: + case SHM_STAT_ANY: + case SHM_STAT: + err = shmctl_stat(ns, shmid, cmd, &sem64); + if (err < 0) + return err; + if (copy_compat_shmid_to_user(uptr, &sem64, version)) + err = -EFAULT; + return err; + + case IPC_SET: + if (copy_compat_shmid_from_user(&sem64, uptr, version)) + return -EFAULT; + fallthrough; + case IPC_RMID: + return shmctl_down(ns, shmid, cmd, &sem64); + case SHM_LOCK: + case SHM_UNLOCK: + return shmctl_do_lock(ns, shmid, cmd); + default: + return -EINVAL; + } + return err; +} + +COMPAT_SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, void __user *, uptr) +{ + return compat_ksys_shmctl(shmid, cmd, uptr, IPC_64); +} + +#ifdef CONFIG_ARCH_WANT_COMPAT_IPC_PARSE_VERSION +long compat_ksys_old_shmctl(int shmid, int cmd, void __user *uptr) +{ + int version = compat_ipc_parse_version(&cmd); + + return compat_ksys_shmctl(shmid, cmd, uptr, version); +} + +COMPAT_SYSCALL_DEFINE3(old_shmctl, int, shmid, int, cmd, void __user *, uptr) +{ + return compat_ksys_old_shmctl(shmid, cmd, uptr); +} +#endif +#endif + +/* + * Fix shmaddr, allocate descriptor, map shm, add attach descriptor to lists. + * + * NOTE! Despite the name, this is NOT a direct system call entrypoint. The + * "raddr" thing points to kernel space, and there has to be a wrapper around + * this. + */ +long do_shmat(int shmid, char __user *shmaddr, int shmflg, + ulong *raddr, unsigned long shmlba) +{ + struct shmid_kernel *shp; + unsigned long addr = (unsigned long)shmaddr; + unsigned long size; + struct file *file, *base; + int err; + unsigned long flags = MAP_SHARED; + unsigned long prot; + int acc_mode; + struct ipc_namespace *ns; + struct shm_file_data *sfd; + int f_flags; + unsigned long populate = 0; + + err = -EINVAL; + if (shmid < 0) + goto out; + + if (addr) { + if (addr & (shmlba - 1)) { + if (shmflg & SHM_RND) { + addr &= ~(shmlba - 1); /* round down */ + + /* + * Ensure that the round-down is non-nil + * when remapping. This can happen for + * cases when addr < shmlba. + */ + if (!addr && (shmflg & SHM_REMAP)) + goto out; + } else +#ifndef __ARCH_FORCE_SHMLBA + if (addr & ~PAGE_MASK) +#endif + goto out; + } + + flags |= MAP_FIXED; + } else if ((shmflg & SHM_REMAP)) + goto out; + + if (shmflg & SHM_RDONLY) { + prot = PROT_READ; + acc_mode = S_IRUGO; + f_flags = O_RDONLY; + } else { + prot = PROT_READ | PROT_WRITE; + acc_mode = S_IRUGO | S_IWUGO; + f_flags = O_RDWR; + } + if (shmflg & SHM_EXEC) { + prot |= PROT_EXEC; + acc_mode |= S_IXUGO; + } + + /* + * We cannot rely on the fs check since SYSV IPC does have an + * additional creator id... + */ + ns = current->nsproxy->ipc_ns; + rcu_read_lock(); + shp = shm_obtain_object_check(ns, shmid); + if (IS_ERR(shp)) { + err = PTR_ERR(shp); + goto out_unlock; + } + + err = -EACCES; + if (ipcperms(ns, &shp->shm_perm, acc_mode)) + goto out_unlock; + + err = security_shm_shmat(&shp->shm_perm, shmaddr, shmflg); + if (err) + goto out_unlock; + + ipc_lock_object(&shp->shm_perm); + + /* check if shm_destroy() is tearing down shp */ + if (!ipc_valid_object(&shp->shm_perm)) { + ipc_unlock_object(&shp->shm_perm); + err = -EIDRM; + goto out_unlock; + } + + /* + * We need to take a reference to the real shm file to prevent the + * pointer from becoming stale in cases where the lifetime of the outer + * file extends beyond that of the shm segment. It's not usually + * possible, but it can happen during remap_file_pages() emulation as + * that unmaps the memory, then does ->mmap() via file reference only. + * We'll deny the ->mmap() if the shm segment was since removed, but to + * detect shm ID reuse we need to compare the file pointers. + */ + base = get_file(shp->shm_file); + shp->shm_nattch++; + size = i_size_read(file_inode(base)); + ipc_unlock_object(&shp->shm_perm); + rcu_read_unlock(); + + err = -ENOMEM; + sfd = kzalloc_obj(*sfd); + if (!sfd) { + fput(base); + goto out_nattch; + } + + file = alloc_file_clone(base, f_flags, + is_file_hugepages(base) ? + &shm_file_operations_huge : + &shm_file_operations); + err = PTR_ERR(file); + if (IS_ERR(file)) { + kfree(sfd); + fput(base); + goto out_nattch; + } + + sfd->id = shp->shm_perm.id; + sfd->ns = get_ipc_ns(ns); + sfd->file = base; + sfd->vm_ops = NULL; + file->private_data = sfd; + + err = security_mmap_file(file, prot, flags); + if (err) + goto out_fput; + + if (mmap_write_lock_killable(current->mm)) { + err = -EINTR; + goto out_fput; + } + + if (addr && !(shmflg & SHM_REMAP)) { + err = -EINVAL; + if (addr + size < addr) + goto invalid; + + if (find_vma_intersection(current->mm, addr, addr + size)) + goto invalid; + } + + addr = do_mmap(file, addr, size, prot, flags, EMPTY_VMA_FLAGS, 0, + &populate, NULL); + *raddr = addr; + err = 0; + if (IS_ERR_VALUE(addr)) + err = (long)addr; +invalid: + mmap_write_unlock(current->mm); + if (populate) + mm_populate(addr, populate); + +out_fput: + fput(file); + +out_nattch: + down_write(&shm_ids(ns).rwsem); + shp = shm_lock(ns, shmid); + shp->shm_nattch--; + + if (shm_may_destroy(shp)) + shm_destroy(ns, shp); + else + shm_unlock(shp); + up_write(&shm_ids(ns).rwsem); + return err; + +out_unlock: + rcu_read_unlock(); +out: + return err; +} + +SYSCALL_DEFINE3(shmat, int, shmid, char __user *, shmaddr, int, shmflg) +{ + unsigned long ret; + long err; + + err = do_shmat(shmid, shmaddr, shmflg, &ret, SHMLBA); + if (err) + return err; + force_successful_syscall_return(); + return (long)ret; +} + +#ifdef CONFIG_COMPAT + +#ifndef COMPAT_SHMLBA +#define COMPAT_SHMLBA SHMLBA +#endif + +COMPAT_SYSCALL_DEFINE3(shmat, int, shmid, compat_uptr_t, shmaddr, int, shmflg) +{ + unsigned long ret; + long err; + + err = do_shmat(shmid, compat_ptr(shmaddr), shmflg, &ret, COMPAT_SHMLBA); + if (err) + return err; + force_successful_syscall_return(); + return (long)ret; +} +#endif + +/* + * detach and kill segment if marked destroyed. + * The work is done in shm_close. + */ +long ksys_shmdt(char __user *shmaddr) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + unsigned long addr = (unsigned long)shmaddr; + int retval = -EINVAL; +#ifdef CONFIG_MMU + loff_t size = 0; + struct file *file; + VMA_ITERATOR(vmi, mm, addr); +#endif + + if (addr & ~PAGE_MASK) + return retval; + + if (mmap_write_lock_killable(mm)) + return -EINTR; + + /* + * This function tries to be smart and unmap shm segments that + * were modified by partial mlock or munmap calls: + * - It first determines the size of the shm segment that should be + * unmapped: It searches for a vma that is backed by shm and that + * started at address shmaddr. It records it's size and then unmaps + * it. + * - Then it unmaps all shm vmas that started at shmaddr and that + * are within the initially determined size and that are from the + * same shm segment from which we determined the size. + * Errors from do_munmap are ignored: the function only fails if + * it's called with invalid parameters or if it's called to unmap + * a part of a vma. Both calls in this function are for full vmas, + * the parameters are directly copied from the vma itself and always + * valid - therefore do_munmap cannot fail. (famous last words?) + */ + /* + * If it had been mremap()'d, the starting address would not + * match the usual checks anyway. So assume all vma's are + * above the starting address given. + */ + +#ifdef CONFIG_MMU + for_each_vma(vmi, vma) { + /* + * Check if the starting address would match, i.e. it's + * a fragment created by mprotect() and/or munmap(), or it + * otherwise it starts at this address with no hassles. + */ + if ((vma->vm_ops == &shm_vm_ops) && + (vma->vm_start - addr)/PAGE_SIZE == vma->vm_pgoff) { + + /* + * Record the file of the shm segment being + * unmapped. With mremap(), someone could place + * page from another segment but with equal offsets + * in the range we are unmapping. + */ + file = vma->vm_file; + size = i_size_read(file_inode(vma->vm_file)); + do_vmi_align_munmap(&vmi, vma, mm, vma->vm_start, + vma->vm_end, NULL, false); + /* + * We discovered the size of the shm segment, so + * break out of here and fall through to the next + * loop that uses the size information to stop + * searching for matching vma's. + */ + retval = 0; + vma = vma_next(&vmi); + break; + } + } + + /* + * We need look no further than the maximum address a fragment + * could possibly have landed at. Also cast things to loff_t to + * prevent overflows and make comparisons vs. equal-width types. + */ + size = PAGE_ALIGN(size); + while (vma && (loff_t)(vma->vm_end - addr) <= size) { + /* finding a matching vma now does not alter retval */ + if ((vma->vm_ops == &shm_vm_ops) && + ((vma->vm_start - addr)/PAGE_SIZE == vma->vm_pgoff) && + (vma->vm_file == file)) { + do_vmi_align_munmap(&vmi, vma, mm, vma->vm_start, + vma->vm_end, NULL, false); + } + + vma = vma_next(&vmi); + } + +#else /* CONFIG_MMU */ + vma = vma_lookup(mm, addr); + /* under NOMMU conditions, the exact address to be destroyed must be + * given + */ + if (vma && vma->vm_start == addr && vma->vm_ops == &shm_vm_ops) { + do_munmap(mm, vma->vm_start, vma->vm_end - vma->vm_start, NULL); + retval = 0; + } + +#endif + + mmap_write_unlock(mm); + return retval; +} + +SYSCALL_DEFINE1(shmdt, char __user *, shmaddr) +{ + return ksys_shmdt(shmaddr); +} + +#ifdef CONFIG_PROC_FS +static int sysvipc_shm_proc_show(struct seq_file *s, void *it) +{ + struct pid_namespace *pid_ns = ipc_seq_pid_ns(s); + struct user_namespace *user_ns = seq_user_ns(s); + struct kern_ipc_perm *ipcp = it; + struct shmid_kernel *shp; + unsigned long rss = 0, swp = 0; + + shp = container_of(ipcp, struct shmid_kernel, shm_perm); + shm_add_rss_swap(shp, &rss, &swp); + +#if BITS_PER_LONG <= 32 +#define SIZE_SPEC "%10lu" +#else +#define SIZE_SPEC "%21lu" +#endif + + seq_printf(s, + "%10d %10d %4o " SIZE_SPEC " %5u %5u " + "%5lu %5u %5u %5u %5u %10llu %10llu %10llu " + SIZE_SPEC " " SIZE_SPEC "\n", + shp->shm_perm.key, + shp->shm_perm.id, + shp->shm_perm.mode, + shp->shm_segsz, + pid_nr_ns(shp->shm_cprid, pid_ns), + pid_nr_ns(shp->shm_lprid, pid_ns), + shp->shm_nattch, + from_kuid_munged(user_ns, shp->shm_perm.uid), + from_kgid_munged(user_ns, shp->shm_perm.gid), + from_kuid_munged(user_ns, shp->shm_perm.cuid), + from_kgid_munged(user_ns, shp->shm_perm.cgid), + shp->shm_atim, + shp->shm_dtim, + shp->shm_ctim, + rss * PAGE_SIZE, + swp * PAGE_SIZE); + + return 0; +} +#endif diff --git a/ipc/syscall.c b/ipc/syscall.c new file mode 100644 index 000000000..dfb0e988d --- /dev/null +++ b/ipc/syscall.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * sys_ipc() is the old de-multiplexer for the SysV IPC calls. + * + * This is really horribly ugly, and new architectures should just wire up + * the individual syscalls instead. + */ +#include <linux/unistd.h> +#include <linux/syscalls.h> +#include <linux/security.h> +#include <linux/ipc_namespace.h> +#include "util.h" + +#ifdef __ARCH_WANT_SYS_IPC +#include <linux/errno.h> +#include <linux/ipc.h> +#include <linux/shm.h> +#include <linux/uaccess.h> + +int ksys_ipc(unsigned int call, int first, unsigned long second, + unsigned long third, void __user * ptr, long fifth) +{ + int version, ret; + + version = call >> 16; /* hack for backward compatibility */ + call &= 0xffff; + + switch (call) { + case SEMOP: + return ksys_semtimedop(first, (struct sembuf __user *)ptr, + second, NULL); + case SEMTIMEDOP: + if (IS_ENABLED(CONFIG_64BIT)) + return ksys_semtimedop(first, ptr, second, + (const struct __kernel_timespec __user *)fifth); + else if (IS_ENABLED(CONFIG_COMPAT_32BIT_TIME)) + return compat_ksys_semtimedop(first, ptr, second, + (const struct old_timespec32 __user *)fifth); + else + return -ENOSYS; + + case SEMGET: + return ksys_semget(first, second, third); + case SEMCTL: { + unsigned long arg; + if (!ptr) + return -EINVAL; + if (get_user(arg, (unsigned long __user *) ptr)) + return -EFAULT; + return ksys_old_semctl(first, second, third, arg); + } + + case MSGSND: + return ksys_msgsnd(first, (struct msgbuf __user *) ptr, + second, third); + case MSGRCV: + switch (version) { + case 0: { + struct ipc_kludge tmp; + if (!ptr) + return -EINVAL; + + if (copy_from_user(&tmp, + (struct ipc_kludge __user *) ptr, + sizeof(tmp))) + return -EFAULT; + return ksys_msgrcv(first, tmp.msgp, second, + tmp.msgtyp, third); + } + default: + return ksys_msgrcv(first, + (struct msgbuf __user *) ptr, + second, fifth, third); + } + case MSGGET: + return ksys_msgget((key_t) first, second); + case MSGCTL: + return ksys_old_msgctl(first, second, + (struct msqid_ds __user *)ptr); + + case SHMAT: + switch (version) { + default: { + unsigned long raddr; + ret = do_shmat(first, (char __user *)ptr, + second, &raddr, SHMLBA); + if (ret) + return ret; + return put_user(raddr, (unsigned long __user *) third); + } + case 1: + /* + * This was the entry point for kernel-originating calls + * from iBCS2 in 2.2 days. + */ + return -EINVAL; + } + case SHMDT: + return ksys_shmdt((char __user *)ptr); + case SHMGET: + return ksys_shmget(first, second, third); + case SHMCTL: + return ksys_old_shmctl(first, second, + (struct shmid_ds __user *) ptr); + default: + return -ENOSYS; + } +} + +SYSCALL_DEFINE6(ipc, unsigned int, call, int, first, unsigned long, second, + unsigned long, third, void __user *, ptr, long, fifth) +{ + return ksys_ipc(call, first, second, third, ptr, fifth); +} +#endif + +#ifdef CONFIG_COMPAT +#include <linux/compat.h> + +#ifndef COMPAT_SHMLBA +#define COMPAT_SHMLBA SHMLBA +#endif + +struct compat_ipc_kludge { + compat_uptr_t msgp; + compat_long_t msgtyp; +}; + +#ifdef CONFIG_ARCH_WANT_OLD_COMPAT_IPC +int compat_ksys_ipc(u32 call, int first, int second, + u32 third, compat_uptr_t ptr, u32 fifth) +{ + int version; + u32 pad; + + version = call >> 16; /* hack for backward compatibility */ + call &= 0xffff; + + switch (call) { + case SEMOP: + /* struct sembuf is the same on 32 and 64bit :)) */ + return ksys_semtimedop(first, compat_ptr(ptr), second, NULL); + case SEMTIMEDOP: + if (!IS_ENABLED(CONFIG_COMPAT_32BIT_TIME)) + return -ENOSYS; + return compat_ksys_semtimedop(first, compat_ptr(ptr), second, + compat_ptr(fifth)); + case SEMGET: + return ksys_semget(first, second, third); + case SEMCTL: + if (!ptr) + return -EINVAL; + if (get_user(pad, (u32 __user *) compat_ptr(ptr))) + return -EFAULT; + return compat_ksys_old_semctl(first, second, third, pad); + + case MSGSND: + return compat_ksys_msgsnd(first, ptr, second, third); + + case MSGRCV: { + void __user *uptr = compat_ptr(ptr); + + if (first < 0 || second < 0) + return -EINVAL; + + if (!version) { + struct compat_ipc_kludge ipck; + if (!uptr) + return -EINVAL; + if (copy_from_user(&ipck, uptr, sizeof(ipck))) + return -EFAULT; + return compat_ksys_msgrcv(first, ipck.msgp, second, + ipck.msgtyp, third); + } + return compat_ksys_msgrcv(first, ptr, second, fifth, third); + } + case MSGGET: + return ksys_msgget(first, second); + case MSGCTL: + return compat_ksys_old_msgctl(first, second, compat_ptr(ptr)); + + case SHMAT: { + int err; + unsigned long raddr; + + if (version == 1) + return -EINVAL; + err = do_shmat(first, compat_ptr(ptr), second, &raddr, + COMPAT_SHMLBA); + if (err < 0) + return err; + return put_user(raddr, (compat_ulong_t __user *)compat_ptr(third)); + } + case SHMDT: + return ksys_shmdt(compat_ptr(ptr)); + case SHMGET: + return ksys_shmget(first, (unsigned int)second, third); + case SHMCTL: + return compat_ksys_old_shmctl(first, second, compat_ptr(ptr)); + } + + return -ENOSYS; +} + +COMPAT_SYSCALL_DEFINE6(ipc, u32, call, int, first, int, second, + u32, third, compat_uptr_t, ptr, u32, fifth) +{ + return compat_ksys_ipc(call, first, second, third, ptr, fifth); +} +#endif +#endif diff --git a/ipc/util.c b/ipc/util.c new file mode 100644 index 000000000..1737d776b --- /dev/null +++ b/ipc/util.c @@ -0,0 +1,928 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * linux/ipc/util.c + * Copyright (C) 1992 Krishna Balasubramanian + * + * Sep 1997 - Call suser() last after "normal" permission checks so we + * get BSD style process accounting right. + * Occurs in several places in the IPC code. + * Chris Evans, <chris@ferret.lmh.ox.ac.uk> + * Nov 1999 - ipc helper functions, unified SMP locking + * Manfred Spraul <manfred@colorfullife.com> + * Oct 2002 - One lock per IPC id. RCU ipc_free for lock-free grow_ary(). + * Mingming Cao <cmm@us.ibm.com> + * Mar 2006 - support for audit of ipc object properties + * Dustin Kirkland <dustin.kirkland@us.ibm.com> + * Jun 2006 - namespaces ssupport + * OpenVZ, SWsoft Inc. + * Pavel Emelianov <xemul@openvz.org> + * + * General sysv ipc locking scheme: + * rcu_read_lock() + * obtain the ipc object (kern_ipc_perm) by looking up the id in an idr + * tree. + * - perform initial checks (capabilities, auditing and permission, + * etc). + * - perform read-only operations, such as INFO command, that + * do not demand atomicity + * acquire the ipc lock (kern_ipc_perm.lock) through + * ipc_lock_object() + * - perform read-only operations that demand atomicity, + * such as STAT command. + * - perform data updates, such as SET, RMID commands and + * mechanism-specific operations (semop/semtimedop, + * msgsnd/msgrcv, shmat/shmdt). + * drop the ipc lock, through ipc_unlock_object(). + * rcu_read_unlock() + * + * The ids->rwsem must be taken when: + * - creating, removing and iterating the existing entries in ipc + * identifier sets. + * - iterating through files under /proc/sysvipc/ + * + * Note that sems have a special fast path that avoids kern_ipc_perm.lock - + * see sem_lock(). + */ + +#include <linux/mm.h> +#include <linux/shm.h> +#include <linux/init.h> +#include <linux/msg.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/notifier.h> +#include <linux/capability.h> +#include <linux/highuid.h> +#include <linux/security.h> +#include <linux/rcupdate.h> +#include <linux/workqueue.h> +#include <linux/seq_file.h> +#include <linux/proc_fs.h> +#include <linux/audit.h> +#include <linux/nsproxy.h> +#include <linux/rwsem.h> +#include <linux/memory.h> +#include <linux/ipc_namespace.h> +#include <linux/rhashtable.h> +#include <linux/log2.h> + +#include <asm/unistd.h> + +#include "util.h" + +struct ipc_proc_iface { + const char *path; + const char *header; + int ids; + int (*show)(struct seq_file *, void *); +}; + +/** + * ipc_init - initialise ipc subsystem + * + * The various sysv ipc resources (semaphores, messages and shared + * memory) are initialised. + * + * A callback routine is registered into the memory hotplug notifier + * chain: since msgmni scales to lowmem this callback routine will be + * called upon successful memory add / remove to recompute msmgni. + */ +static int __init ipc_init(void) +{ + proc_mkdir("sysvipc", NULL); + sem_init(); + msg_init(); + shm_init(); + + return 0; +} +device_initcall(ipc_init); + +static const struct rhashtable_params ipc_kht_params = { + .head_offset = offsetof(struct kern_ipc_perm, khtnode), + .key_offset = offsetof(struct kern_ipc_perm, key), + .key_len = sizeof_field(struct kern_ipc_perm, key), + .automatic_shrinking = true, +}; + +/** + * ipc_init_ids - initialise ipc identifiers + * @ids: ipc identifier set + * + * Set up the sequence range to use for the ipc identifier range (limited + * below ipc_mni) then initialise the keys hashtable and ids idr. + */ +void ipc_init_ids(struct ipc_ids *ids) +{ + ids->in_use = 0; + ids->seq = 0; + init_rwsem(&ids->rwsem); + rhashtable_init(&ids->key_ht, &ipc_kht_params); + idr_init(&ids->ipcs_idr); + ids->max_idx = -1; + ids->last_idx = -1; +#ifdef CONFIG_CHECKPOINT_RESTORE + ids->next_id = -1; +#endif +} + +#ifdef CONFIG_PROC_FS +static const struct proc_ops sysvipc_proc_ops; +/** + * ipc_init_proc_interface - create a proc interface for sysipc types using a seq_file interface. + * @path: Path in procfs + * @header: Banner to be printed at the beginning of the file. + * @ids: ipc id table to iterate. + * @show: show routine. + */ +void __init ipc_init_proc_interface(const char *path, const char *header, + int ids, int (*show)(struct seq_file *, void *)) +{ + struct proc_dir_entry *pde; + struct ipc_proc_iface *iface; + + iface = kmalloc_obj(*iface); + if (!iface) + return; + iface->path = path; + iface->header = header; + iface->ids = ids; + iface->show = show; + + pde = proc_create_data(path, + S_IRUGO, /* world readable */ + NULL, /* parent dir */ + &sysvipc_proc_ops, + iface); + if (!pde) + kfree(iface); +} +#endif + +/** + * ipc_findkey - find a key in an ipc identifier set + * @ids: ipc identifier set + * @key: key to find + * + * Returns the locked pointer to the ipc structure if found or NULL + * otherwise. If key is found ipc points to the owning ipc structure + * + * Called with writer ipc_ids.rwsem held. + */ +static struct kern_ipc_perm *ipc_findkey(struct ipc_ids *ids, key_t key) +{ + struct kern_ipc_perm *ipcp; + + ipcp = rhashtable_lookup_fast(&ids->key_ht, &key, + ipc_kht_params); + if (!ipcp) + return NULL; + + rcu_read_lock(); + ipc_lock_object(ipcp); + return ipcp; +} + +/* + * Insert new IPC object into idr tree, and set sequence number and id + * in the correct order. + * Especially: + * - the sequence number must be set before inserting the object into the idr, + * because the sequence number is accessed without a lock. + * - the id can/must be set after inserting the object into the idr. + * All accesses must be done after getting kern_ipc_perm.lock. + * + * The caller must own kern_ipc_perm.lock.of the new object. + * On error, the function returns a (negative) error code. + * + * To conserve sequence number space, especially with extended ipc_mni, + * the sequence number is incremented only when the returned ID is less than + * the last one. + */ +static inline int ipc_idr_alloc(struct ipc_ids *ids, struct kern_ipc_perm *new) +{ + int idx, next_id = -1; + +#ifdef CONFIG_CHECKPOINT_RESTORE + next_id = ids->next_id; + ids->next_id = -1; +#endif + + /* + * As soon as a new object is inserted into the idr, + * ipc_obtain_object_idr() or ipc_obtain_object_check() can find it, + * and the lockless preparations for ipc operations can start. + * This means especially: permission checks, audit calls, allocation + * of undo structures, ... + * + * Thus the object must be fully initialized, and if something fails, + * then the full tear-down sequence must be followed. + * (i.e.: set new->deleted, reduce refcount, call_rcu()) + */ + + if (next_id < 0) { /* !CHECKPOINT_RESTORE or next_id is unset */ + int max_idx; + + max_idx = max(ids->in_use*3/2, ipc_min_cycle); + max_idx = min(max_idx, ipc_mni); + + /* allocate the idx, with a NULL struct kern_ipc_perm */ + idx = idr_alloc_cyclic(&ids->ipcs_idr, NULL, 0, max_idx, + GFP_NOWAIT); + + if (idx >= 0) { + /* + * idx got allocated successfully. + * Now calculate the sequence number and set the + * pointer for real. + */ + if (idx <= ids->last_idx) { + ids->seq++; + if (ids->seq >= ipcid_seq_max()) + ids->seq = 0; + } + ids->last_idx = idx; + + new->seq = ids->seq; + /* no need for smp_wmb(), this is done + * inside idr_replace, as part of + * rcu_assign_pointer + */ + idr_replace(&ids->ipcs_idr, new, idx); + } + } else { + new->seq = ipcid_to_seqx(next_id); + idx = idr_alloc(&ids->ipcs_idr, new, ipcid_to_idx(next_id), + ipc_mni, GFP_NOWAIT); + } + if (idx >= 0) + new->id = (new->seq << ipcmni_seq_shift()) + idx; + return idx; +} + +/** + * ipc_addid - add an ipc identifier + * @ids: ipc identifier set + * @new: new ipc permission set + * @limit: limit for the number of used ids + * + * Add an entry 'new' to the ipc ids idr. The permissions object is + * initialised and the first free entry is set up and the index assigned + * is returned. The 'new' entry is returned in a locked state on success. + * + * On failure the entry is not locked and a negative err-code is returned. + * The caller must use ipc_rcu_putref() to free the identifier. + * + * Called with writer ipc_ids.rwsem held. + */ +int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int limit) +{ + kuid_t euid; + kgid_t egid; + int idx, err; + + /* 1) Initialize the refcount so that ipc_rcu_putref works */ + refcount_set(&new->refcount, 1); + + if (limit > ipc_mni) + limit = ipc_mni; + + if (ids->in_use >= limit) + return -ENOSPC; + + idr_preload(GFP_KERNEL); + + spin_lock_init(&new->lock); + rcu_read_lock(); + spin_lock(&new->lock); + + current_euid_egid(&euid, &egid); + new->cuid = new->uid = euid; + new->gid = new->cgid = egid; + + new->deleted = false; + + idx = ipc_idr_alloc(ids, new); + idr_preload_end(); + + if (idx >= 0 && new->key != IPC_PRIVATE) { + err = rhashtable_insert_fast(&ids->key_ht, &new->khtnode, + ipc_kht_params); + if (err < 0) { + idr_remove(&ids->ipcs_idr, idx); + idx = err; + } + } + if (idx < 0) { + new->deleted = true; + spin_unlock(&new->lock); + rcu_read_unlock(); + return idx; + } + + ids->in_use++; + if (idx > ids->max_idx) + ids->max_idx = idx; + return idx; +} + +/** + * ipcget_new - create a new ipc object + * @ns: ipc namespace + * @ids: ipc identifier set + * @ops: the actual creation routine to call + * @params: its parameters + * + * This routine is called by sys_msgget, sys_semget() and sys_shmget() + * when the key is IPC_PRIVATE. + */ +static int ipcget_new(struct ipc_namespace *ns, struct ipc_ids *ids, + const struct ipc_ops *ops, struct ipc_params *params) +{ + int err; + + down_write(&ids->rwsem); + err = ops->getnew(ns, params); + up_write(&ids->rwsem); + return err; +} + +/** + * ipc_check_perms - check security and permissions for an ipc object + * @ns: ipc namespace + * @ipcp: ipc permission set + * @ops: the actual security routine to call + * @params: its parameters + * + * This routine is called by sys_msgget(), sys_semget() and sys_shmget() + * when the key is not IPC_PRIVATE and that key already exists in the + * ds IDR. + * + * On success, the ipc id is returned. + * + * It is called with ipc_ids.rwsem and ipcp->lock held. + */ +static int ipc_check_perms(struct ipc_namespace *ns, + struct kern_ipc_perm *ipcp, + const struct ipc_ops *ops, + struct ipc_params *params) +{ + int err; + + if (ipcperms(ns, ipcp, params->flg)) + err = -EACCES; + else { + err = ops->associate(ipcp, params->flg); + if (!err) + err = ipcp->id; + } + + return err; +} + +/** + * ipcget_public - get an ipc object or create a new one + * @ns: ipc namespace + * @ids: ipc identifier set + * @ops: the actual creation routine to call + * @params: its parameters + * + * This routine is called by sys_msgget, sys_semget() and sys_shmget() + * when the key is not IPC_PRIVATE. + * It adds a new entry if the key is not found and does some permission + * / security checkings if the key is found. + * + * On success, the ipc id is returned. + */ +static int ipcget_public(struct ipc_namespace *ns, struct ipc_ids *ids, + const struct ipc_ops *ops, struct ipc_params *params) +{ + struct kern_ipc_perm *ipcp; + int flg = params->flg; + int err; + + /* + * Take the lock as a writer since we are potentially going to add + * a new entry + read locks are not "upgradable" + */ + down_write(&ids->rwsem); + ipcp = ipc_findkey(ids, params->key); + if (ipcp == NULL) { + /* key not used */ + if (!(flg & IPC_CREAT)) + err = -ENOENT; + else + err = ops->getnew(ns, params); + } else { + /* ipc object has been locked by ipc_findkey() */ + + if (flg & IPC_CREAT && flg & IPC_EXCL) + err = -EEXIST; + else { + err = 0; + if (ops->more_checks) + err = ops->more_checks(ipcp, params); + if (!err) + /* + * ipc_check_perms returns the IPC id on + * success + */ + err = ipc_check_perms(ns, ipcp, ops, params); + } + ipc_unlock(ipcp); + } + up_write(&ids->rwsem); + + return err; +} + +/** + * ipc_kht_remove - remove an ipc from the key hashtable + * @ids: ipc identifier set + * @ipcp: ipc perm structure containing the key to remove + * + * ipc_ids.rwsem (as a writer) and the spinlock for this ID are held + * before this function is called, and remain locked on the exit. + */ +static void ipc_kht_remove(struct ipc_ids *ids, struct kern_ipc_perm *ipcp) +{ + if (ipcp->key != IPC_PRIVATE) + WARN_ON_ONCE(rhashtable_remove_fast(&ids->key_ht, &ipcp->khtnode, + ipc_kht_params)); +} + +/** + * ipc_search_maxidx - search for the highest assigned index + * @ids: ipc identifier set + * @limit: known upper limit for highest assigned index + * + * The function determines the highest assigned index in @ids. It is intended + * to be called when ids->max_idx needs to be updated. + * Updating ids->max_idx is necessary when the current highest index ipc + * object is deleted. + * If no ipc object is allocated, then -1 is returned. + * + * ipc_ids.rwsem needs to be held by the caller. + */ +static int ipc_search_maxidx(struct ipc_ids *ids, int limit) +{ + int tmpidx; + int i; + int retval; + + i = ilog2(limit+1); + + retval = 0; + for (; i >= 0; i--) { + tmpidx = retval | (1<<i); + /* + * "0" is a possible index value, thus search using + * e.g. 15,7,3,1,0 instead of 16,8,4,2,1. + */ + tmpidx = tmpidx-1; + if (idr_get_next(&ids->ipcs_idr, &tmpidx)) + retval |= (1<<i); + } + return retval - 1; +} + +/** + * ipc_rmid - remove an ipc identifier + * @ids: ipc identifier set + * @ipcp: ipc perm structure containing the identifier to remove + * + * ipc_ids.rwsem (as a writer) and the spinlock for this ID are held + * before this function is called, and remain locked on the exit. + */ +void ipc_rmid(struct ipc_ids *ids, struct kern_ipc_perm *ipcp) +{ + int idx = ipcid_to_idx(ipcp->id); + + WARN_ON_ONCE(idr_remove(&ids->ipcs_idr, idx) != ipcp); + ipc_kht_remove(ids, ipcp); + ids->in_use--; + ipcp->deleted = true; + + if (unlikely(idx == ids->max_idx)) { + idx = ids->max_idx-1; + if (idx >= 0) + idx = ipc_search_maxidx(ids, idx); + ids->max_idx = idx; + } +} + +/** + * ipc_set_key_private - switch the key of an existing ipc to IPC_PRIVATE + * @ids: ipc identifier set + * @ipcp: ipc perm structure containing the key to modify + * + * ipc_ids.rwsem (as a writer) and the spinlock for this ID are held + * before this function is called, and remain locked on the exit. + */ +void ipc_set_key_private(struct ipc_ids *ids, struct kern_ipc_perm *ipcp) +{ + ipc_kht_remove(ids, ipcp); + ipcp->key = IPC_PRIVATE; +} + +bool ipc_rcu_getref(struct kern_ipc_perm *ptr) +{ + return refcount_inc_not_zero(&ptr->refcount); +} + +void ipc_rcu_putref(struct kern_ipc_perm *ptr, + void (*func)(struct rcu_head *head)) +{ + if (!refcount_dec_and_test(&ptr->refcount)) + return; + + call_rcu(&ptr->rcu, func); +} + +/** + * ipcperms - check ipc permissions + * @ns: ipc namespace + * @ipcp: ipc permission set + * @flag: desired permission set + * + * Check user, group, other permissions for access + * to ipc resources. return 0 if allowed + * + * @flag will most probably be 0 or ``S_...UGO`` from <linux/stat.h> + */ +int ipcperms(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp, short flag) +{ + kuid_t euid = current_euid(); + int requested_mode, granted_mode; + + audit_ipc_obj(ipcp); + requested_mode = (flag >> 6) | (flag >> 3) | flag; + granted_mode = ipcp->mode; + if (uid_eq(euid, ipcp->cuid) || + uid_eq(euid, ipcp->uid)) + granted_mode >>= 6; + else if (in_group_p(ipcp->cgid) || in_group_p(ipcp->gid)) + granted_mode >>= 3; + /* is there some bit set in requested_mode but not in granted_mode? */ + if ((requested_mode & ~granted_mode & 0007) && + !ns_capable(ns->user_ns, CAP_IPC_OWNER)) + return -1; + + return security_ipc_permission(ipcp, flag); +} + +/* + * Functions to convert between the kern_ipc_perm structure and the + * old/new ipc_perm structures + */ + +/** + * kernel_to_ipc64_perm - convert kernel ipc permissions to user + * @in: kernel permissions + * @out: new style ipc permissions + * + * Turn the kernel object @in into a set of permissions descriptions + * for returning to userspace (@out). + */ +void kernel_to_ipc64_perm(struct kern_ipc_perm *in, struct ipc64_perm *out) +{ + out->key = in->key; + out->uid = from_kuid_munged(current_user_ns(), in->uid); + out->gid = from_kgid_munged(current_user_ns(), in->gid); + out->cuid = from_kuid_munged(current_user_ns(), in->cuid); + out->cgid = from_kgid_munged(current_user_ns(), in->cgid); + out->mode = in->mode; + out->seq = in->seq; +} + +/** + * ipc64_perm_to_ipc_perm - convert new ipc permissions to old + * @in: new style ipc permissions + * @out: old style ipc permissions + * + * Turn the new style permissions object @in into a compatibility + * object and store it into the @out pointer. + */ +void ipc64_perm_to_ipc_perm(struct ipc64_perm *in, struct ipc_perm *out) +{ + out->key = in->key; + SET_UID(out->uid, in->uid); + SET_GID(out->gid, in->gid); + SET_UID(out->cuid, in->cuid); + SET_GID(out->cgid, in->cgid); + out->mode = in->mode; + out->seq = in->seq; +} + +/** + * ipc_obtain_object_idr - Look for an id in the ipc ids idr and + * return associated ipc object. + * @ids: ipc identifier set + * @id: ipc id to look for + * + * Call inside the RCU critical section. + * The ipc object is *not* locked on exit. + */ +struct kern_ipc_perm *ipc_obtain_object_idr(struct ipc_ids *ids, int id) +{ + struct kern_ipc_perm *out; + int idx = ipcid_to_idx(id); + + out = idr_find(&ids->ipcs_idr, idx); + if (!out) + return ERR_PTR(-EINVAL); + + return out; +} + +/** + * ipc_obtain_object_check - Similar to ipc_obtain_object_idr() but + * also checks the ipc object sequence number. + * @ids: ipc identifier set + * @id: ipc id to look for + * + * Call inside the RCU critical section. + * The ipc object is *not* locked on exit. + */ +struct kern_ipc_perm *ipc_obtain_object_check(struct ipc_ids *ids, int id) +{ + struct kern_ipc_perm *out = ipc_obtain_object_idr(ids, id); + + if (IS_ERR(out)) + goto out; + + if (ipc_checkid(out, id)) + return ERR_PTR(-EINVAL); +out: + return out; +} + +/** + * ipcget - Common sys_*get() code + * @ns: namespace + * @ids: ipc identifier set + * @ops: operations to be called on ipc object creation, permission checks + * and further checks + * @params: the parameters needed by the previous operations. + * + * Common routine called by sys_msgget(), sys_semget() and sys_shmget(). + */ +int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids, + const struct ipc_ops *ops, struct ipc_params *params) +{ + if (params->key == IPC_PRIVATE) + return ipcget_new(ns, ids, ops, params); + else + return ipcget_public(ns, ids, ops, params); +} + +/** + * ipc_update_perm - update the permissions of an ipc object + * @in: the permission given as input. + * @out: the permission of the ipc to set. + */ +int ipc_update_perm(struct ipc64_perm *in, struct kern_ipc_perm *out) +{ + kuid_t uid = make_kuid(current_user_ns(), in->uid); + kgid_t gid = make_kgid(current_user_ns(), in->gid); + if (!uid_valid(uid) || !gid_valid(gid)) + return -EINVAL; + + out->uid = uid; + out->gid = gid; + out->mode = (out->mode & ~S_IRWXUGO) + | (in->mode & S_IRWXUGO); + + return 0; +} + +/** + * ipcctl_obtain_check - retrieve an ipc object and check permissions + * @ns: ipc namespace + * @ids: the table of ids where to look for the ipc + * @id: the id of the ipc to retrieve + * @cmd: the cmd to check + * @perm: the permission to set + * @extra_perm: one extra permission parameter used by msq + * + * This function does some common audit and permissions check for some IPC_XXX + * cmd and is called from semctl_down, shmctl_down and msgctl_down. + * + * It: + * - retrieves the ipc object with the given id in the given table. + * - performs some audit and permission check, depending on the given cmd + * - returns a pointer to the ipc object or otherwise, the corresponding + * error. + * + * Call holding the both the rwsem and the rcu read lock. + */ +struct kern_ipc_perm *ipcctl_obtain_check(struct ipc_namespace *ns, + struct ipc_ids *ids, int id, int cmd, + struct ipc64_perm *perm, int extra_perm) +{ + kuid_t euid; + int err = -EPERM; + struct kern_ipc_perm *ipcp; + + ipcp = ipc_obtain_object_check(ids, id); + if (IS_ERR(ipcp)) { + err = PTR_ERR(ipcp); + goto err; + } + + audit_ipc_obj(ipcp); + if (cmd == IPC_SET) + audit_ipc_set_perm(extra_perm, perm->uid, + perm->gid, perm->mode); + + euid = current_euid(); + if (uid_eq(euid, ipcp->cuid) || uid_eq(euid, ipcp->uid) || + ns_capable(ns->user_ns, CAP_SYS_ADMIN)) + return ipcp; /* successful lookup */ +err: + return ERR_PTR(err); +} + +#ifdef CONFIG_ARCH_WANT_IPC_PARSE_VERSION + + +/** + * ipc_parse_version - ipc call version + * @cmd: pointer to command + * + * Return IPC_64 for new style IPC and IPC_OLD for old style IPC. + * The @cmd value is turned from an encoding command and version into + * just the command code. + */ +int ipc_parse_version(int *cmd) +{ + if (*cmd & IPC_64) { + *cmd ^= IPC_64; + return IPC_64; + } else { + return IPC_OLD; + } +} + +#endif /* CONFIG_ARCH_WANT_IPC_PARSE_VERSION */ + +#ifdef CONFIG_PROC_FS +struct ipc_proc_iter { + struct ipc_namespace *ns; + struct pid_namespace *pid_ns; + struct ipc_proc_iface *iface; +}; + +struct pid_namespace *ipc_seq_pid_ns(struct seq_file *s) +{ + struct ipc_proc_iter *iter = s->private; + return iter->pid_ns; +} + +/** + * sysvipc_find_ipc - Find and lock the ipc structure based on seq pos + * @ids: ipc identifier set + * @pos: expected position + * + * The function finds an ipc structure, based on the sequence file + * position @pos. If there is no ipc structure at position @pos, then + * the successor is selected. + * If a structure is found, then it is locked (both rcu_read_lock() and + * ipc_lock_object()) and @pos is set to the position needed to locate + * the found ipc structure. + * If nothing is found (i.e. EOF), @pos is not modified. + * + * The function returns the found ipc structure, or NULL at EOF. + */ +static struct kern_ipc_perm *sysvipc_find_ipc(struct ipc_ids *ids, loff_t *pos) +{ + int tmpidx; + struct kern_ipc_perm *ipc; + + /* convert from position to idr index -> "-1" */ + tmpidx = *pos - 1; + + ipc = idr_get_next(&ids->ipcs_idr, &tmpidx); + if (ipc != NULL) { + rcu_read_lock(); + ipc_lock_object(ipc); + + /* convert from idr index to position -> "+1" */ + *pos = tmpidx + 1; + } + return ipc; +} + +static void *sysvipc_proc_next(struct seq_file *s, void *it, loff_t *pos) +{ + struct ipc_proc_iter *iter = s->private; + struct ipc_proc_iface *iface = iter->iface; + struct kern_ipc_perm *ipc = it; + + /* If we had an ipc id locked before, unlock it */ + if (ipc && ipc != SEQ_START_TOKEN) + ipc_unlock(ipc); + + /* Next -> search for *pos+1 */ + (*pos)++; + return sysvipc_find_ipc(&iter->ns->ids[iface->ids], pos); +} + +/* + * File positions: pos 0 -> header, pos n -> ipc idx = n - 1. + * SeqFile iterator: iterator value locked ipc pointer or SEQ_TOKEN_START. + */ +static void *sysvipc_proc_start(struct seq_file *s, loff_t *pos) +{ + struct ipc_proc_iter *iter = s->private; + struct ipc_proc_iface *iface = iter->iface; + struct ipc_ids *ids; + + ids = &iter->ns->ids[iface->ids]; + + /* + * Take the lock - this will be released by the corresponding + * call to stop(). + */ + down_read(&ids->rwsem); + + /* pos < 0 is invalid */ + if (*pos < 0) + return NULL; + + /* pos == 0 means header */ + if (*pos == 0) + return SEQ_START_TOKEN; + + /* Otherwise return the correct ipc structure */ + return sysvipc_find_ipc(ids, pos); +} + +static void sysvipc_proc_stop(struct seq_file *s, void *it) +{ + struct kern_ipc_perm *ipc = it; + struct ipc_proc_iter *iter = s->private; + struct ipc_proc_iface *iface = iter->iface; + struct ipc_ids *ids; + + /* If we had a locked structure, release it */ + if (ipc && ipc != SEQ_START_TOKEN) + ipc_unlock(ipc); + + ids = &iter->ns->ids[iface->ids]; + /* Release the lock we took in start() */ + up_read(&ids->rwsem); +} + +static int sysvipc_proc_show(struct seq_file *s, void *it) +{ + struct ipc_proc_iter *iter = s->private; + struct ipc_proc_iface *iface = iter->iface; + + if (it == SEQ_START_TOKEN) { + seq_puts(s, iface->header); + return 0; + } + + return iface->show(s, it); +} + +static const struct seq_operations sysvipc_proc_seqops = { + .start = sysvipc_proc_start, + .stop = sysvipc_proc_stop, + .next = sysvipc_proc_next, + .show = sysvipc_proc_show, +}; + +static int sysvipc_proc_open(struct inode *inode, struct file *file) +{ + struct ipc_proc_iter *iter; + + iter = __seq_open_private(file, &sysvipc_proc_seqops, sizeof(*iter)); + if (!iter) + return -ENOMEM; + + iter->iface = pde_data(inode); + iter->ns = get_ipc_ns(current->nsproxy->ipc_ns); + iter->pid_ns = get_pid_ns(task_active_pid_ns(current)); + + return 0; +} + +static int sysvipc_proc_release(struct inode *inode, struct file *file) +{ + struct seq_file *seq = file->private_data; + struct ipc_proc_iter *iter = seq->private; + put_ipc_ns(iter->ns); + put_pid_ns(iter->pid_ns); + return seq_release_private(inode, file); +} + +static const struct proc_ops sysvipc_proc_ops = { + .proc_flags = PROC_ENTRY_PERMANENT, + .proc_open = sysvipc_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = sysvipc_proc_release, +}; +#endif /* CONFIG_PROC_FS */ diff --git a/ipc/util.h b/ipc/util.h new file mode 100644 index 000000000..a55d6cebe --- /dev/null +++ b/ipc/util.h @@ -0,0 +1,292 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * linux/ipc/util.h + * Copyright (C) 1999 Christoph Rohland + * + * ipc helper functions (c) 1999 Manfred Spraul <manfred@colorfullife.com> + * namespaces support. 2006 OpenVZ, SWsoft Inc. + * Pavel Emelianov <xemul@openvz.org> + */ + +#ifndef _IPC_UTIL_H +#define _IPC_UTIL_H + +#include <linux/unistd.h> +#include <linux/err.h> +#include <linux/ipc_namespace.h> +#include <linux/pid.h> + +/* + * The IPC ID contains 2 separate numbers - index and sequence number. + * By default, + * bits 0-14: index (32k, 15 bits) + * bits 15-30: sequence number (64k, 16 bits) + * + * When IPCMNI extension mode is turned on, the composition changes: + * bits 0-23: index (16M, 24 bits) + * bits 24-30: sequence number (128, 7 bits) + */ +#define IPCMNI_SHIFT 15 +#define IPCMNI_EXTEND_SHIFT 24 +#define IPCMNI_EXTEND_MIN_CYCLE (RADIX_TREE_MAP_SIZE * RADIX_TREE_MAP_SIZE) +#define IPCMNI (1 << IPCMNI_SHIFT) +#define IPCMNI_EXTEND (1 << IPCMNI_EXTEND_SHIFT) + +#ifdef CONFIG_SYSVIPC_SYSCTL +extern int ipc_mni; +extern int ipc_mni_shift; +extern int ipc_min_cycle; + +#define ipcmni_seq_shift() ipc_mni_shift +#define IPCMNI_IDX_MASK ((1 << ipc_mni_shift) - 1) + +#else /* CONFIG_SYSVIPC_SYSCTL */ + +#define ipc_mni IPCMNI +#define ipc_min_cycle ((int)RADIX_TREE_MAP_SIZE) +#define ipcmni_seq_shift() IPCMNI_SHIFT +#define IPCMNI_IDX_MASK ((1 << IPCMNI_SHIFT) - 1) +#endif /* CONFIG_SYSVIPC_SYSCTL */ + +void sem_init(void); +void msg_init(void); +void shm_init(void); + +struct ipc_namespace; +struct pid_namespace; + +#ifdef CONFIG_POSIX_MQUEUE +extern void mq_clear_sbinfo(struct ipc_namespace *ns); +#else +static inline void mq_clear_sbinfo(struct ipc_namespace *ns) { } +#endif + +#ifdef CONFIG_SYSVIPC +void sem_init_ns(struct ipc_namespace *ns); +int msg_init_ns(struct ipc_namespace *ns); +void shm_init_ns(struct ipc_namespace *ns); + +void sem_exit_ns(struct ipc_namespace *ns); +void msg_exit_ns(struct ipc_namespace *ns); +void shm_exit_ns(struct ipc_namespace *ns); +#else +static inline void sem_init_ns(struct ipc_namespace *ns) { } +static inline int msg_init_ns(struct ipc_namespace *ns) { return 0; } +static inline void shm_init_ns(struct ipc_namespace *ns) { } + +static inline void sem_exit_ns(struct ipc_namespace *ns) { } +static inline void msg_exit_ns(struct ipc_namespace *ns) { } +static inline void shm_exit_ns(struct ipc_namespace *ns) { } +#endif + +/* + * Structure that holds the parameters needed by the ipc operations + * (see after) + */ +struct ipc_params { + key_t key; + int flg; + union { + size_t size; /* for shared memories */ + int nsems; /* for semaphores */ + } u; /* holds the getnew() specific param */ +}; + +/* + * Structure that holds some ipc operations. This structure is used to unify + * the calls to sys_msgget(), sys_semget(), sys_shmget() + * . routine to call to create a new ipc object. Can be one of newque, + * newary, newseg + * . routine to call to check permissions for a new ipc object. + * Can be one of security_msg_associate, security_sem_associate, + * security_shm_associate + * . routine to call for an extra check if needed + */ +struct ipc_ops { + int (*getnew)(struct ipc_namespace *, struct ipc_params *); + int (*associate)(struct kern_ipc_perm *, int); + int (*more_checks)(struct kern_ipc_perm *, struct ipc_params *); +}; + +struct seq_file; +struct ipc_ids; + +void ipc_init_ids(struct ipc_ids *ids); +#ifdef CONFIG_PROC_FS +void __init ipc_init_proc_interface(const char *path, const char *header, + int ids, int (*show)(struct seq_file *, void *)); +struct pid_namespace *ipc_seq_pid_ns(struct seq_file *); +#else +#define ipc_init_proc_interface(path, header, ids, show) do {} while (0) +#endif + +#define IPC_SEM_IDS 0 +#define IPC_MSG_IDS 1 +#define IPC_SHM_IDS 2 + +#define ipcid_to_idx(id) ((id) & IPCMNI_IDX_MASK) +#define ipcid_to_seqx(id) ((id) >> ipcmni_seq_shift()) +#define ipcid_seq_max() (INT_MAX >> ipcmni_seq_shift()) + +/* must be called with ids->rwsem acquired for writing */ +int ipc_addid(struct ipc_ids *, struct kern_ipc_perm *, int); + +/* must be called with both locks acquired. */ +void ipc_rmid(struct ipc_ids *, struct kern_ipc_perm *); + +/* must be called with both locks acquired. */ +void ipc_set_key_private(struct ipc_ids *, struct kern_ipc_perm *); + +/* must be called with ipcp locked */ +int ipcperms(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp, short flg); + +/** + * ipc_get_maxidx - get the highest assigned index + * @ids: ipc identifier set + * + * The function returns the highest assigned index for @ids. The function + * doesn't scan the idr tree, it uses a cached value. + * + * Called with ipc_ids.rwsem held for reading. + */ +static inline int ipc_get_maxidx(struct ipc_ids *ids) +{ + if (ids->in_use == 0) + return -1; + + if (ids->in_use == ipc_mni) + return ipc_mni - 1; + + return ids->max_idx; +} + +/* + * For allocation that need to be freed by RCU. + * Objects are reference counted, they start with reference count 1. + * getref increases the refcount, the putref call that reduces the recount + * to 0 schedules the rcu destruction. Caller must guarantee locking. + * + * refcount is initialized by ipc_addid(), before that point call_rcu() + * must be used. + */ +bool ipc_rcu_getref(struct kern_ipc_perm *ptr); +void ipc_rcu_putref(struct kern_ipc_perm *ptr, + void (*func)(struct rcu_head *head)); + +struct kern_ipc_perm *ipc_obtain_object_idr(struct ipc_ids *ids, int id); + +void kernel_to_ipc64_perm(struct kern_ipc_perm *in, struct ipc64_perm *out); +void ipc64_perm_to_ipc_perm(struct ipc64_perm *in, struct ipc_perm *out); +int ipc_update_perm(struct ipc64_perm *in, struct kern_ipc_perm *out); +struct kern_ipc_perm *ipcctl_obtain_check(struct ipc_namespace *ns, + struct ipc_ids *ids, int id, int cmd, + struct ipc64_perm *perm, int extra_perm); + +static inline void ipc_update_pid(struct pid **pos, struct pid *pid) +{ + struct pid *old = *pos; + if (old != pid) { + *pos = get_pid(pid); + put_pid(old); + } +} + +#ifdef CONFIG_ARCH_WANT_IPC_PARSE_VERSION +int ipc_parse_version(int *cmd); +#endif + +extern void free_msg(struct msg_msg *msg); +extern struct msg_msg *load_msg(const void __user *src, size_t len); +extern struct msg_msg *copy_msg(struct msg_msg *src, struct msg_msg *dst); +extern int store_msg(void __user *dest, struct msg_msg *msg, size_t len); + +static inline int ipc_checkid(struct kern_ipc_perm *ipcp, int id) +{ + return ipcid_to_seqx(id) != ipcp->seq; +} + +static inline void ipc_lock_object(struct kern_ipc_perm *perm) +{ + spin_lock(&perm->lock); +} + +static inline void ipc_unlock_object(struct kern_ipc_perm *perm) +{ + spin_unlock(&perm->lock); +} + +static inline void ipc_assert_locked_object(struct kern_ipc_perm *perm) +{ + assert_spin_locked(&perm->lock); +} + +static inline void ipc_unlock(struct kern_ipc_perm *perm) +{ + ipc_unlock_object(perm); + rcu_read_unlock(); +} + +/* + * ipc_valid_object() - helper to sort out IPC_RMID races for codepaths + * where the respective ipc_ids.rwsem is not being held down. + * Checks whether the ipc object is still around or if it's gone already, as + * ipc_rmid() may have already freed the ID while the ipc lock was spinning. + * Needs to be called with kern_ipc_perm.lock held -- exception made for one + * checkpoint case at sys_semtimedop() as noted in code commentary. + */ +static inline bool ipc_valid_object(struct kern_ipc_perm *perm) +{ + return !perm->deleted; +} + +struct kern_ipc_perm *ipc_obtain_object_check(struct ipc_ids *ids, int id); +int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids, + const struct ipc_ops *ops, struct ipc_params *params); +void free_ipcs(struct ipc_namespace *ns, struct ipc_ids *ids, + void (*free)(struct ipc_namespace *, struct kern_ipc_perm *)); + +static inline int sem_check_semmni(struct ipc_namespace *ns) { + /* + * Check semmni range [0, ipc_mni] + * semmni is the last element of sem_ctls[4] array + */ + return ((ns->sem_ctls[3] < 0) || (ns->sem_ctls[3] > ipc_mni)) + ? -ERANGE : 0; +} + +#ifdef CONFIG_COMPAT +#include <linux/compat.h> +struct compat_ipc_perm { + key_t key; + __compat_uid_t uid; + __compat_gid_t gid; + __compat_uid_t cuid; + __compat_gid_t cgid; + compat_mode_t mode; + unsigned short seq; +}; + +void to_compat_ipc_perm(struct compat_ipc_perm *, struct ipc64_perm *); +void to_compat_ipc64_perm(struct compat_ipc64_perm *, struct ipc64_perm *); +int get_compat_ipc_perm(struct ipc64_perm *, struct compat_ipc_perm __user *); +int get_compat_ipc64_perm(struct ipc64_perm *, + struct compat_ipc64_perm __user *); + +static inline int compat_ipc_parse_version(int *cmd) +{ + int version = *cmd & IPC_64; + *cmd &= ~IPC_64; + return version; +} + +long compat_ksys_old_semctl(int semid, int semnum, int cmd, int arg); +long compat_ksys_old_msgctl(int msqid, int cmd, void __user *uptr); +long compat_ksys_msgrcv(int msqid, compat_uptr_t msgp, compat_ssize_t msgsz, + compat_long_t msgtyp, int msgflg); +long compat_ksys_msgsnd(int msqid, compat_uptr_t msgp, + compat_ssize_t msgsz, int msgflg); +long compat_ksys_old_shmctl(int shmid, int cmd, void __user *uptr); + +#endif + +#endif |
