diff options
author | Ingo Molnar <mingo@kernel.org> | 2017-10-20 13:06:52 +0200 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2017-10-20 13:06:52 +0200 |
commit | 967535223f9a8d95c187a8728480b569164cd4f4 (patch) | |
tree | b68a403701555a747b25c903d8136d86a1a1c133 /ipc | |
parent | 5b65c4677a57a1d4414212f9995aa0e46a21ff80 (diff) | |
parent | ce56a86e2ade45d052b3228cdfebe913a1ae7381 (diff) | |
download | op-kernel-dev-967535223f9a8d95c187a8728480b569164cd4f4.zip op-kernel-dev-967535223f9a8d95c187a8728480b569164cd4f4.tar.gz |
Merge branch 'x86/urgent' into x86/mm, to pick up fixes
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Diffstat (limited to 'ipc')
-rw-r--r-- | ipc/compat.c | 740 | ||||
-rw-r--r-- | ipc/mqueue.c | 28 | ||||
-rw-r--r-- | ipc/msg.c | 376 | ||||
-rw-r--r-- | ipc/msgutil.c | 2 | ||||
-rw-r--r-- | ipc/namespace.c | 24 | ||||
-rw-r--r-- | ipc/sem.c | 372 | ||||
-rw-r--r-- | ipc/shm.c | 545 | ||||
-rw-r--r-- | ipc/syscall.c | 90 | ||||
-rw-r--r-- | ipc/util.c | 106 | ||||
-rw-r--r-- | ipc/util.h | 51 |
10 files changed, 1149 insertions, 1185 deletions
diff --git a/ipc/compat.c b/ipc/compat.c index 9b3c85f..b17bf93 100644 --- a/ipc/compat.c +++ b/ipc/compat.c @@ -34,724 +34,48 @@ #include "util.h" -struct compat_msgbuf { - compat_long_t mtype; - char mtext[1]; -}; - -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; -}; - -struct compat_semid_ds { - struct compat_ipc_perm sem_perm; - compat_time_t sem_otime; - compat_time_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; -}; - -struct compat_msqid_ds { - struct compat_ipc_perm msg_perm; - compat_uptr_t msg_first; - compat_uptr_t msg_last; - compat_time_t msg_stime; - compat_time_t msg_rtime; - compat_time_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; -}; - -struct compat_shmid_ds { - struct compat_ipc_perm shm_perm; - int shm_segsz; - compat_time_t shm_atime; - compat_time_t shm_dtime; - compat_time_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_ipc_kludge { - compat_uptr_t msgp; - compat_long_t msgtyp; -}; - -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 inline int compat_ipc_parse_version(int *cmd) -{ -#ifdef CONFIG_ARCH_WANT_COMPAT_IPC_PARSE_VERSION - int version = *cmd & IPC_64; - - /* this is tricky: architectures that have support for the old - * ipc structures in 64 bit binaries need to have IPC_64 set - * in cmd, the others need to have it cleared */ -#ifndef ipc_parse_version - *cmd |= IPC_64; -#else - *cmd &= ~IPC_64; -#endif - return version; -#else - /* With the asm-generic APIs, we always use the 64-bit versions. */ - return IPC_64; -#endif -} - -static inline int __get_compat_ipc64_perm(struct ipc64_perm *p64, - struct compat_ipc64_perm __user *up64) -{ - int err; - - err = __get_user(p64->uid, &up64->uid); - err |= __get_user(p64->gid, &up64->gid); - err |= __get_user(p64->mode, &up64->mode); - return err; -} - -static inline int __get_compat_ipc_perm(struct ipc64_perm *p, - struct compat_ipc_perm __user *up) -{ - int err; - - err = __get_user(p->uid, &up->uid); - err |= __get_user(p->gid, &up->gid); - err |= __get_user(p->mode, &up->mode); - return err; -} - -static inline int __put_compat_ipc64_perm(struct ipc64_perm *p64, - struct compat_ipc64_perm __user *up64) +int get_compat_ipc64_perm(struct ipc64_perm *to, + struct compat_ipc64_perm __user *from) { - int err; - - err = __put_user(p64->key, &up64->key); - err |= __put_user(p64->uid, &up64->uid); - err |= __put_user(p64->gid, &up64->gid); - err |= __put_user(p64->cuid, &up64->cuid); - err |= __put_user(p64->cgid, &up64->cgid); - err |= __put_user(p64->mode, &up64->mode); - err |= __put_user(p64->seq, &up64->seq); - return err; -} - -static inline int __put_compat_ipc_perm(struct ipc64_perm *p, - struct compat_ipc_perm __user *uip) -{ - int err; - __compat_uid_t u; - __compat_gid_t g; - - err = __put_user(p->key, &uip->key); - SET_UID(u, p->uid); - err |= __put_user(u, &uip->uid); - SET_GID(g, p->gid); - err |= __put_user(g, &uip->gid); - SET_UID(u, p->cuid); - err |= __put_user(u, &uip->cuid); - SET_GID(g, p->cgid); - err |= __put_user(g, &uip->cgid); - err |= __put_user(p->mode, &uip->mode); - err |= __put_user(p->seq, &uip->seq); - return err; -} - -static inline int get_compat_semid64_ds(struct semid64_ds *sem64, - struct compat_semid64_ds __user *up64) -{ - if (!access_ok(VERIFY_READ, up64, sizeof(*up64))) - return -EFAULT; - return __get_compat_ipc64_perm(&sem64->sem_perm, &up64->sem_perm); -} - -static inline int get_compat_semid_ds(struct semid64_ds *s, - struct compat_semid_ds __user *up) -{ - if (!access_ok(VERIFY_READ, up, sizeof(*up))) + struct compat_ipc64_perm v; + if (copy_from_user(&v, from, sizeof(v))) return -EFAULT; - return __get_compat_ipc_perm(&s->sem_perm, &up->sem_perm); + to->uid = v.uid; + to->gid = v.gid; + to->mode = v.mode; + return 0; } -static inline int put_compat_semid64_ds(struct semid64_ds *sem64, - struct compat_semid64_ds __user *up64) +int get_compat_ipc_perm(struct ipc64_perm *to, + struct compat_ipc_perm __user *from) { - int err; - - if (!access_ok(VERIFY_WRITE, up64, sizeof(*up64))) + struct compat_ipc_perm v; + if (copy_from_user(&v, from, sizeof(v))) return -EFAULT; - err = __put_compat_ipc64_perm(&sem64->sem_perm, &up64->sem_perm); - err |= __put_user(sem64->sem_otime, &up64->sem_otime); - err |= __put_user(sem64->sem_ctime, &up64->sem_ctime); - err |= __put_user(sem64->sem_nsems, &up64->sem_nsems); - return err; + to->uid = v.uid; + to->gid = v.gid; + to->mode = v.mode; + return 0; } -static inline int put_compat_semid_ds(struct semid64_ds *s, - struct compat_semid_ds __user *up) +void to_compat_ipc64_perm(struct compat_ipc64_perm *to, struct ipc64_perm *from) { - int err; - - if (!access_ok(VERIFY_WRITE, up, sizeof(*up))) - return -EFAULT; - err = __put_compat_ipc_perm(&s->sem_perm, &up->sem_perm); - err |= __put_user(s->sem_otime, &up->sem_otime); - err |= __put_user(s->sem_ctime, &up->sem_ctime); - err |= __put_user(s->sem_nsems, &up->sem_nsems); - return err; + 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; } -static long do_compat_semctl(int first, int second, int third, u32 pad) +void to_compat_ipc_perm(struct compat_ipc_perm *to, struct ipc64_perm *from) { - unsigned long fourth; - int err, err2; - struct semid64_ds sem64; - struct semid64_ds __user *up64; - int version = compat_ipc_parse_version(&third); - - memset(&sem64, 0, sizeof(sem64)); - - if ((third & (~IPC_64)) == SETVAL) -#ifdef __BIG_ENDIAN - fourth = (unsigned long)pad << 32; -#else - fourth = pad; -#endif - else - fourth = (unsigned long)compat_ptr(pad); - switch (third & (~IPC_64)) { - case IPC_INFO: - case IPC_RMID: - case SEM_INFO: - case GETVAL: - case GETPID: - case GETNCNT: - case GETZCNT: - case GETALL: - case SETVAL: - case SETALL: - err = sys_semctl(first, second, third, fourth); - break; - - case IPC_STAT: - case SEM_STAT: - up64 = compat_alloc_user_space(sizeof(sem64)); - fourth = (unsigned long)up64; - err = sys_semctl(first, second, third, fourth); - if (err < 0) - break; - if (copy_from_user(&sem64, up64, sizeof(sem64))) - err2 = -EFAULT; - else if (version == IPC_64) - err2 = put_compat_semid64_ds(&sem64, compat_ptr(pad)); - else - err2 = put_compat_semid_ds(&sem64, compat_ptr(pad)); - if (err2) - err = -EFAULT; - break; - - case IPC_SET: - if (version == IPC_64) - err = get_compat_semid64_ds(&sem64, compat_ptr(pad)); - else - err = get_compat_semid_ds(&sem64, compat_ptr(pad)); - - up64 = compat_alloc_user_space(sizeof(sem64)); - if (copy_to_user(up64, &sem64, sizeof(sem64))) - err = -EFAULT; - if (err) - break; - - fourth = (unsigned long)up64; - err = sys_semctl(first, second, third, fourth); - break; - - default: - err = -EINVAL; - break; - } - return err; -} - -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; -} - -#ifndef COMPAT_SHMLBA -#define COMPAT_SHMLBA SHMLBA -#endif - -#ifdef CONFIG_ARCH_WANT_OLD_COMPAT_IPC -COMPAT_SYSCALL_DEFINE6(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 sys_semtimedop(first, compat_ptr(ptr), second, NULL); - case SEMTIMEDOP: - return compat_sys_semtimedop(first, compat_ptr(ptr), second, - compat_ptr(fifth)); - case SEMGET: - return sys_semget(first, second, third); - case SEMCTL: - if (!ptr) - return -EINVAL; - if (get_user(pad, (u32 __user *) compat_ptr(ptr))) - return -EFAULT; - return do_compat_semctl(first, second, third, pad); - - case MSGSND: { - struct compat_msgbuf __user *up = compat_ptr(ptr); - compat_long_t type; - - if (first < 0 || second < 0) - return -EINVAL; - - if (get_user(type, &up->mtype)) - return -EFAULT; - - return do_msgsnd(first, type, up->mtext, 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; - uptr = compat_ptr(ipck.msgp); - fifth = ipck.msgtyp; - } - return do_msgrcv(first, uptr, second, (s32)fifth, third, - compat_do_msg_fill); - } - case MSGGET: - return sys_msgget(first, second); - case MSGCTL: - return compat_sys_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 *)compat_ptr(third)); - } - case SHMDT: - return sys_shmdt(compat_ptr(ptr)); - case SHMGET: - return sys_shmget(first, (unsigned)second, third); - case SHMCTL: - return compat_sys_shmctl(first, second, compat_ptr(ptr)); - } - - return -ENOSYS; -} -#endif - -COMPAT_SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, int, arg) -{ - return do_compat_semctl(semid, semnum, cmd, arg); -} - -COMPAT_SYSCALL_DEFINE4(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_DEFINE5(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); -} - -static inline int get_compat_msqid64(struct msqid64_ds *m64, - struct compat_msqid64_ds __user *up64) -{ - int err; - - if (!access_ok(VERIFY_READ, up64, sizeof(*up64))) - return -EFAULT; - err = __get_compat_ipc64_perm(&m64->msg_perm, &up64->msg_perm); - err |= __get_user(m64->msg_qbytes, &up64->msg_qbytes); - return err; -} - -static inline int get_compat_msqid(struct msqid64_ds *m, - struct compat_msqid_ds __user *up) -{ - int err; - - if (!access_ok(VERIFY_READ, up, sizeof(*up))) - return -EFAULT; - err = __get_compat_ipc_perm(&m->msg_perm, &up->msg_perm); - err |= __get_user(m->msg_qbytes, &up->msg_qbytes); - return err; -} - -static inline int put_compat_msqid64_ds(struct msqid64_ds *m64, - struct compat_msqid64_ds __user *up64) -{ - int err; - - if (!access_ok(VERIFY_WRITE, up64, sizeof(*up64))) - return -EFAULT; - err = __put_compat_ipc64_perm(&m64->msg_perm, &up64->msg_perm); - err |= __put_user(m64->msg_stime, &up64->msg_stime); - err |= __put_user(m64->msg_rtime, &up64->msg_rtime); - err |= __put_user(m64->msg_ctime, &up64->msg_ctime); - err |= __put_user(m64->msg_cbytes, &up64->msg_cbytes); - err |= __put_user(m64->msg_qnum, &up64->msg_qnum); - err |= __put_user(m64->msg_qbytes, &up64->msg_qbytes); - err |= __put_user(m64->msg_lspid, &up64->msg_lspid); - err |= __put_user(m64->msg_lrpid, &up64->msg_lrpid); - return err; -} - -static inline int put_compat_msqid_ds(struct msqid64_ds *m, - struct compat_msqid_ds __user *up) -{ - int err; - - if (!access_ok(VERIFY_WRITE, up, sizeof(*up))) - return -EFAULT; - err = __put_compat_ipc_perm(&m->msg_perm, &up->msg_perm); - err |= __put_user(m->msg_stime, &up->msg_stime); - err |= __put_user(m->msg_rtime, &up->msg_rtime); - err |= __put_user(m->msg_ctime, &up->msg_ctime); - err |= __put_user(m->msg_cbytes, &up->msg_cbytes); - err |= __put_user(m->msg_qnum, &up->msg_qnum); - err |= __put_user(m->msg_qbytes, &up->msg_qbytes); - err |= __put_user(m->msg_lspid, &up->msg_lspid); - err |= __put_user(m->msg_lrpid, &up->msg_lrpid); - return err; -} - -COMPAT_SYSCALL_DEFINE3(msgctl, int, first, int, second, void __user *, uptr) -{ - int err, err2; - struct msqid64_ds m64; - int version = compat_ipc_parse_version(&second); - void __user *p; - - memset(&m64, 0, sizeof(m64)); - - switch (second & (~IPC_64)) { - case IPC_INFO: - case IPC_RMID: - case MSG_INFO: - err = sys_msgctl(first, second, uptr); - break; - - case IPC_SET: - if (version == IPC_64) - err = get_compat_msqid64(&m64, uptr); - else - err = get_compat_msqid(&m64, uptr); - - if (err) - break; - p = compat_alloc_user_space(sizeof(m64)); - if (copy_to_user(p, &m64, sizeof(m64))) - err = -EFAULT; - else - err = sys_msgctl(first, second, p); - break; - - case IPC_STAT: - case MSG_STAT: - p = compat_alloc_user_space(sizeof(m64)); - err = sys_msgctl(first, second, p); - if (err < 0) - break; - if (copy_from_user(&m64, p, sizeof(m64))) - err2 = -EFAULT; - else if (version == IPC_64) - err2 = put_compat_msqid64_ds(&m64, uptr); - else - err2 = put_compat_msqid_ds(&m64, uptr); - if (err2) - err = -EFAULT; - break; - - default: - err = -EINVAL; - break; - } - return err; -} - -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; -} - -static inline int get_compat_shmid64_ds(struct shmid64_ds *sem64, - struct compat_shmid64_ds __user *up64) -{ - if (!access_ok(VERIFY_READ, up64, sizeof(*up64))) - return -EFAULT; - return __get_compat_ipc64_perm(&sem64->shm_perm, &up64->shm_perm); -} - -static inline int get_compat_shmid_ds(struct shmid64_ds *s, - struct compat_shmid_ds __user *up) -{ - if (!access_ok(VERIFY_READ, up, sizeof(*up))) - return -EFAULT; - return __get_compat_ipc_perm(&s->shm_perm, &up->shm_perm); -} - -static inline int put_compat_shmid64_ds(struct shmid64_ds *sem64, - struct compat_shmid64_ds __user *up64) -{ - int err; - - if (!access_ok(VERIFY_WRITE, up64, sizeof(*up64))) - return -EFAULT; - err = __put_compat_ipc64_perm(&sem64->shm_perm, &up64->shm_perm); - err |= __put_user(sem64->shm_atime, &up64->shm_atime); - err |= __put_user(sem64->shm_dtime, &up64->shm_dtime); - err |= __put_user(sem64->shm_ctime, &up64->shm_ctime); - err |= __put_user(sem64->shm_segsz, &up64->shm_segsz); - err |= __put_user(sem64->shm_nattch, &up64->shm_nattch); - err |= __put_user(sem64->shm_cpid, &up64->shm_cpid); - err |= __put_user(sem64->shm_lpid, &up64->shm_lpid); - return err; -} - -static inline int put_compat_shmid_ds(struct shmid64_ds *s, - struct compat_shmid_ds __user *up) -{ - int err; - - if (!access_ok(VERIFY_WRITE, up, sizeof(*up))) - return -EFAULT; - err = __put_compat_ipc_perm(&s->shm_perm, &up->shm_perm); - err |= __put_user(s->shm_atime, &up->shm_atime); - err |= __put_user(s->shm_dtime, &up->shm_dtime); - err |= __put_user(s->shm_ctime, &up->shm_ctime); - err |= __put_user(s->shm_segsz, &up->shm_segsz); - err |= __put_user(s->shm_nattch, &up->shm_nattch); - err |= __put_user(s->shm_cpid, &up->shm_cpid); - err |= __put_user(s->shm_lpid, &up->shm_lpid); - return err; -} - -static inline int put_compat_shminfo64(struct shminfo64 *smi, - struct compat_shminfo64 __user *up64) -{ - int err; - - if (!access_ok(VERIFY_WRITE, up64, sizeof(*up64))) - return -EFAULT; - if (smi->shmmax > INT_MAX) - smi->shmmax = INT_MAX; - err = __put_user(smi->shmmax, &up64->shmmax); - err |= __put_user(smi->shmmin, &up64->shmmin); - err |= __put_user(smi->shmmni, &up64->shmmni); - err |= __put_user(smi->shmseg, &up64->shmseg); - err |= __put_user(smi->shmall, &up64->shmall); - return err; -} - -static inline int put_compat_shminfo(struct shminfo64 *smi, - struct shminfo __user *up) -{ - int err; - - if (!access_ok(VERIFY_WRITE, up, sizeof(*up))) - return -EFAULT; - if (smi->shmmax > INT_MAX) - smi->shmmax = INT_MAX; - err = __put_user(smi->shmmax, &up->shmmax); - err |= __put_user(smi->shmmin, &up->shmmin); - err |= __put_user(smi->shmmni, &up->shmmni); - err |= __put_user(smi->shmseg, &up->shmseg); - err |= __put_user(smi->shmall, &up->shmall); - return err; -} - -static inline int put_compat_shm_info(struct shm_info __user *ip, - struct compat_shm_info __user *uip) -{ - int err; - struct shm_info si; - - if (!access_ok(VERIFY_WRITE, uip, sizeof(*uip)) || - copy_from_user(&si, ip, sizeof(si))) - return -EFAULT; - err = __put_user(si.used_ids, &uip->used_ids); - err |= __put_user(si.shm_tot, &uip->shm_tot); - err |= __put_user(si.shm_rss, &uip->shm_rss); - err |= __put_user(si.shm_swp, &uip->shm_swp); - err |= __put_user(si.swap_attempts, &uip->swap_attempts); - err |= __put_user(si.swap_successes, &uip->swap_successes); - return err; -} - -COMPAT_SYSCALL_DEFINE3(shmctl, int, first, int, second, void __user *, uptr) -{ - void __user *p; - struct shmid64_ds sem64; - struct shminfo64 smi; - int err, err2; - int version = compat_ipc_parse_version(&second); - - memset(&sem64, 0, sizeof(sem64)); - - switch (second & (~IPC_64)) { - case IPC_RMID: - case SHM_LOCK: - case SHM_UNLOCK: - err = sys_shmctl(first, second, uptr); - break; - - case IPC_INFO: - p = compat_alloc_user_space(sizeof(smi)); - err = sys_shmctl(first, second, p); - if (err < 0) - break; - if (copy_from_user(&smi, p, sizeof(smi))) - err2 = -EFAULT; - else if (version == IPC_64) - err2 = put_compat_shminfo64(&smi, uptr); - else - err2 = put_compat_shminfo(&smi, uptr); - if (err2) - err = -EFAULT; - break; - - - case IPC_SET: - if (version == IPC_64) - err = get_compat_shmid64_ds(&sem64, uptr); - else - err = get_compat_shmid_ds(&sem64, uptr); - - if (err) - break; - p = compat_alloc_user_space(sizeof(sem64)); - if (copy_to_user(p, &sem64, sizeof(sem64))) - err = -EFAULT; - else - err = sys_shmctl(first, second, p); - break; - - case IPC_STAT: - case SHM_STAT: - p = compat_alloc_user_space(sizeof(sem64)); - err = sys_shmctl(first, second, p); - if (err < 0) - break; - if (copy_from_user(&sem64, p, sizeof(sem64))) - err2 = -EFAULT; - else if (version == IPC_64) - err2 = put_compat_shmid64_ds(&sem64, uptr); - else - err2 = put_compat_shmid_ds(&sem64, uptr); - if (err2) - err = -EFAULT; - break; - - case SHM_INFO: - p = compat_alloc_user_space(sizeof(struct shm_info)); - err = sys_shmctl(first, second, p); - if (err < 0) - break; - err2 = put_compat_shm_info(p, uptr); - if (err2) - err = -EFAULT; - break; - - default: - err = -EINVAL; - break; - } - return err; -} - -COMPAT_SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsems, - unsigned, nsops, - const struct compat_timespec __user *, timeout) -{ - struct timespec __user *ts64; - if (compat_convert_timespec(&ts64, timeout)) - return -EFAULT; - return sys_semtimedop(semid, tsems, nsops, ts64); + 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/mqueue.c b/ipc/mqueue.c index eb1391b..d240256 100644 --- a/ipc/mqueue.c +++ b/ipc/mqueue.c @@ -668,11 +668,11 @@ static void __do_notify(struct mqueue_inode_info *info) } static int prepare_timeout(const struct timespec __user *u_abs_timeout, - struct timespec *ts) + struct timespec64 *ts) { - if (copy_from_user(ts, u_abs_timeout, sizeof(struct timespec))) + if (get_timespec64(ts, u_abs_timeout)) return -EFAULT; - if (!timespec_valid(ts)) + if (!timespec64_valid(ts)) return -EINVAL; return 0; } @@ -962,7 +962,7 @@ static inline void pipelined_receive(struct wake_q_head *wake_q, static int do_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr, size_t msg_len, unsigned int msg_prio, - struct timespec *ts) + struct timespec64 *ts) { struct fd f; struct inode *inode; @@ -979,7 +979,7 @@ static int do_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr, return -EINVAL; if (ts) { - expires = timespec_to_ktime(*ts); + expires = timespec64_to_ktime(*ts); timeout = &expires; } @@ -1080,7 +1080,7 @@ out: static int do_mq_timedreceive(mqd_t mqdes, char __user *u_msg_ptr, size_t msg_len, unsigned int __user *u_msg_prio, - struct timespec *ts) + struct timespec64 *ts) { ssize_t ret; struct msg_msg *msg_ptr; @@ -1092,7 +1092,7 @@ static int do_mq_timedreceive(mqd_t mqdes, char __user *u_msg_ptr, struct posix_msg_tree_node *new_leaf = NULL; if (ts) { - expires = timespec_to_ktime(*ts); + expires = timespec64_to_ktime(*ts); timeout = &expires; } @@ -1184,7 +1184,7 @@ SYSCALL_DEFINE5(mq_timedsend, mqd_t, mqdes, const char __user *, u_msg_ptr, size_t, msg_len, unsigned int, msg_prio, const struct timespec __user *, u_abs_timeout) { - struct timespec ts, *p = NULL; + struct timespec64 ts, *p = NULL; if (u_abs_timeout) { int res = prepare_timeout(u_abs_timeout, &ts); if (res) @@ -1198,7 +1198,7 @@ SYSCALL_DEFINE5(mq_timedreceive, mqd_t, mqdes, char __user *, u_msg_ptr, size_t, msg_len, unsigned int __user *, u_msg_prio, const struct timespec __user *, u_abs_timeout) { - struct timespec ts, *p = NULL; + struct timespec64 ts, *p = NULL; if (u_abs_timeout) { int res = prepare_timeout(u_abs_timeout, &ts); if (res) @@ -1475,11 +1475,11 @@ COMPAT_SYSCALL_DEFINE4(mq_open, const char __user *, u_name, } static int compat_prepare_timeout(const struct compat_timespec __user *p, - struct timespec *ts) + struct timespec64 *ts) { - if (compat_get_timespec(ts, p)) + if (compat_get_timespec64(ts, p)) return -EFAULT; - if (!timespec_valid(ts)) + if (!timespec64_valid(ts)) return -EINVAL; return 0; } @@ -1489,7 +1489,7 @@ COMPAT_SYSCALL_DEFINE5(mq_timedsend, mqd_t, mqdes, compat_size_t, msg_len, unsigned int, msg_prio, const struct compat_timespec __user *, u_abs_timeout) { - struct timespec ts, *p = NULL; + struct timespec64 ts, *p = NULL; if (u_abs_timeout) { int res = compat_prepare_timeout(u_abs_timeout, &ts); if (res) @@ -1504,7 +1504,7 @@ COMPAT_SYSCALL_DEFINE5(mq_timedreceive, mqd_t, mqdes, compat_size_t, msg_len, unsigned int __user *, u_msg_prio, const struct compat_timespec __user *, u_abs_timeout) { - struct timespec ts, *p = NULL; + struct timespec64 ts, *p = NULL; if (u_abs_timeout) { int res = compat_prepare_timeout(u_abs_timeout, &ts); if (res) @@ -133,7 +133,7 @@ static int newque(struct ipc_namespace *ns, struct ipc_params *params) } msq->q_stime = msq->q_rtime = 0; - msq->q_ctime = get_seconds(); + 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 = 0; @@ -361,23 +361,17 @@ copy_msqid_from_user(struct msqid64_ds *out, void __user *buf, int version) * 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 msqid_ds __user *buf, int version) + struct msqid64_ds *msqid64) { struct kern_ipc_perm *ipcp; - struct msqid64_ds uninitialized_var(msqid64); struct msg_queue *msq; int err; - if (cmd == IPC_SET) { - if (copy_msqid_from_user(&msqid64, buf, version)) - return -EFAULT; - } - down_write(&msg_ids(ns).rwsem); rcu_read_lock(); ipcp = ipcctl_pre_down_nolock(ns, &msg_ids(ns), msqid, cmd, - &msqid64.msg_perm, msqid64.msg_qbytes); + &msqid64->msg_perm, msqid64->msg_qbytes); if (IS_ERR(ipcp)) { err = PTR_ERR(ipcp); goto out_unlock1; @@ -399,20 +393,20 @@ static int msgctl_down(struct ipc_namespace *ns, int msqid, int cmd, { DEFINE_WAKE_Q(wake_q); - if (msqid64.msg_qbytes > ns->msg_ctlmnb && + if (msqid64->msg_qbytes > ns->msg_ctlmnb && !capable(CAP_SYS_RESOURCE)) { err = -EPERM; goto out_unlock1; } ipc_lock_object(&msq->q_perm); - err = ipc_update_perm(&msqid64.msg_perm, ipcp); + err = ipc_update_perm(&msqid64->msg_perm, ipcp); if (err) goto out_unlock0; - msq->q_qbytes = msqid64.msg_qbytes; + msq->q_qbytes = msqid64->msg_qbytes; - msq->q_ctime = get_seconds(); + msq->q_ctime = ktime_get_real_seconds(); /* * Sleeping receivers might be excluded by * stricter permissions. @@ -442,111 +436,89 @@ out_up: return err; } -static int msgctl_nolock(struct ipc_namespace *ns, int msqid, - int cmd, int version, void __user *buf) +static int msgctl_info(struct ipc_namespace *ns, int msqid, + int cmd, struct msginfo *msginfo) { int err; - struct msg_queue *msq; - - switch (cmd) { - case IPC_INFO: - case MSG_INFO: - { - struct msginfo msginfo; - int max_id; - - if (!buf) - return -EFAULT; - - /* - * 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; + int max_id; - 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; - msginfo.msgmap = atomic_read(&ns->msg_hdrs); - msginfo.msgtql = atomic_read(&ns->msg_bytes); - } else { - msginfo.msgmap = MSGMAP; - msginfo.msgpool = MSGPOOL; - msginfo.msgtql = MSGTQL; - } - max_id = ipc_get_maxid(&msg_ids(ns)); - up_read(&msg_ids(ns).rwsem); - if (copy_to_user(buf, &msginfo, sizeof(struct msginfo))) - return -EFAULT; - return (max_id < 0) ? 0 : max_id; + /* + * 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; + msginfo->msgmap = atomic_read(&ns->msg_hdrs); + msginfo->msgtql = atomic_read(&ns->msg_bytes); + } else { + msginfo->msgmap = MSGMAP; + msginfo->msgpool = MSGPOOL; + msginfo->msgtql = MSGTQL; } + max_id = ipc_get_maxid(&msg_ids(ns)); + up_read(&msg_ids(ns).rwsem); + return (max_id < 0) ? 0 : max_id; +} - case MSG_STAT: - case IPC_STAT: - { - struct msqid64_ds tbuf; - int success_return; - - if (!buf) - return -EFAULT; - - memset(&tbuf, 0, sizeof(tbuf)); +static int msgctl_stat(struct ipc_namespace *ns, int msqid, + int cmd, struct msqid64_ds *p) +{ + int err; + struct msg_queue *msq; + int success_return; - rcu_read_lock(); - if (cmd == MSG_STAT) { - msq = msq_obtain_object(ns, msqid); - if (IS_ERR(msq)) { - err = PTR_ERR(msq); - goto out_unlock; - } - success_return = msq->q_perm.id; - } else { - msq = msq_obtain_object_check(ns, msqid); - if (IS_ERR(msq)) { - err = PTR_ERR(msq); - goto out_unlock; - } - success_return = 0; - } + memset(p, 0, sizeof(*p)); - err = -EACCES; - if (ipcperms(ns, &msq->q_perm, S_IRUGO)) + rcu_read_lock(); + if (cmd == MSG_STAT) { + msq = msq_obtain_object(ns, msqid); + if (IS_ERR(msq)) { + err = PTR_ERR(msq); goto out_unlock; - - err = security_msg_queue_msgctl(msq, cmd); - if (err) + } + success_return = msq->q_perm.id; + } else { + msq = msq_obtain_object_check(ns, msqid); + if (IS_ERR(msq)) { + err = PTR_ERR(msq); goto out_unlock; + } + success_return = 0; + } - kernel_to_ipc64_perm(&msq->q_perm, &tbuf.msg_perm); - tbuf.msg_stime = msq->q_stime; - tbuf.msg_rtime = msq->q_rtime; - tbuf.msg_ctime = msq->q_ctime; - tbuf.msg_cbytes = msq->q_cbytes; - tbuf.msg_qnum = msq->q_qnum; - tbuf.msg_qbytes = msq->q_qbytes; - tbuf.msg_lspid = msq->q_lspid; - tbuf.msg_lrpid = msq->q_lrpid; - rcu_read_unlock(); + err = -EACCES; + if (ipcperms(ns, &msq->q_perm, S_IRUGO)) + goto out_unlock; - if (copy_msqid_to_user(buf, &tbuf, version)) - return -EFAULT; - return success_return; - } + err = security_msg_queue_msgctl(msq, cmd); + if (err) + 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; + p->msg_cbytes = msq->q_cbytes; + p->msg_qnum = msq->q_qnum; + p->msg_qbytes = msq->q_qbytes; + p->msg_lspid = msq->q_lspid; + p->msg_lrpid = msq->q_lrpid; + rcu_read_unlock(); - default: - return -EINVAL; - } + return success_return; - return err; out_unlock: rcu_read_unlock(); return err; @@ -556,6 +528,8 @@ SYSCALL_DEFINE3(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; @@ -565,18 +539,147 @@ SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf) switch (cmd) { case IPC_INFO: - case MSG_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 IPC_STAT: - return msgctl_nolock(ns, msqid, cmd, version, buf); + 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; + /* fallthru */ case IPC_RMID: - return msgctl_down(ns, msqid, cmd, buf, version); + return msgctl_down(ns, msqid, cmd, &msqid64); default: return -EINVAL; } } +#ifdef CONFIG_COMPAT + +struct compat_msqid_ds { + struct compat_ipc_perm msg_perm; + compat_uptr_t msg_first; + compat_uptr_t msg_last; + compat_time_t msg_stime; + compat_time_t msg_rtime; + compat_time_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 *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 *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 = 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)); + } 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)); + } +} + +COMPAT_SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, void __user *, uptr) +{ + struct ipc_namespace *ns; + int err; + struct msqid64_ds msqid64; + int version = compat_ipc_parse_version(&cmd); + + 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: + 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; + /* fallthru */ + case IPC_RMID: + return msgctl_down(ns, msqid, cmd, &msqid64); + default: + return -EINVAL; + } +} +#endif + static int testmsg(struct msg_msg *msg, long type, int mode) { switch (mode) { @@ -627,7 +730,7 @@ static inline int pipelined_send(struct msg_queue *msq, struct msg_msg *msg, return 0; } -long do_msgsnd(int msqid, long mtype, void __user *mtext, +static long do_msgsnd(int msqid, long mtype, void __user *mtext, size_t msgsz, int msgflg) { struct msg_queue *msq; @@ -750,6 +853,25 @@ SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz, return do_msgsnd(msqid, mtype, msgp->mtext, msgsz, msgflg); } +#ifdef CONFIG_COMPAT + +struct compat_msgbuf { + compat_long_t mtype; + char mtext[1]; +}; + +COMPAT_SYSCALL_DEFINE4(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); +} +#endif + static inline int convert_mode(long *msgtyp, int msgflg) { if (msgflg & MSG_COPY) @@ -846,7 +968,7 @@ static struct msg_msg *find_msg(struct msg_queue *msq, long *msgtyp, int mode) return found ?: ERR_PTR(-EAGAIN); } -long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg, +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; @@ -1010,8 +1132,30 @@ SYSCALL_DEFINE5(msgrcv, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz, return do_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill); } +#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; +} + +COMPAT_SYSCALL_DEFINE5(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); +} +#endif -void msg_init_ns(struct ipc_namespace *ns) +int msg_init_ns(struct ipc_namespace *ns) { ns->msg_ctlmax = MSGMAX; ns->msg_ctlmnb = MSGMNB; @@ -1019,7 +1163,7 @@ void msg_init_ns(struct ipc_namespace *ns) atomic_set(&ns->msg_bytes, 0); atomic_set(&ns->msg_hdrs, 0); - ipc_init_ids(&ns->ids[IPC_MSG_IDS]); + return ipc_init_ids(&ns->ids[IPC_MSG_IDS]); } #ifdef CONFIG_IPC_NS @@ -1027,6 +1171,7 @@ 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); } #endif @@ -1038,7 +1183,7 @@ static int sysvipc_msg_proc_show(struct seq_file *s, void *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 %10lu %10lu %10lu\n", + "%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, @@ -1058,11 +1203,12 @@ static int sysvipc_msg_proc_show(struct seq_file *s, void *it) } #endif -void __init msg_init(void) +int __init msg_init(void) { - msg_init_ns(&init_ipc_ns); + const int err = 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); + return err; } diff --git a/ipc/msgutil.c b/ipc/msgutil.c index bf74eaa..8459802 100644 --- a/ipc/msgutil.c +++ b/ipc/msgutil.c @@ -29,7 +29,7 @@ DEFINE_SPINLOCK(mq_lock); * and not CONFIG_IPC_NS. */ struct ipc_namespace init_ipc_ns = { - .count = ATOMIC_INIT(1), + .count = REFCOUNT_INIT(1), .user_ns = &init_user_ns, .ns.inum = PROC_IPC_INIT_INO, #ifdef CONFIG_IPC_NS diff --git a/ipc/namespace.c b/ipc/namespace.c index b4d80f9..fc850c5 100644 --- a/ipc/namespace.c +++ b/ipc/namespace.c @@ -50,20 +50,32 @@ static struct ipc_namespace *create_ipc_ns(struct user_namespace *user_ns, goto fail_free; ns->ns.ops = &ipcns_operations; - atomic_set(&ns->count, 1); + refcount_set(&ns->count, 1); ns->user_ns = get_user_ns(user_ns); ns->ucounts = ucounts; - err = mq_init_ns(ns); + err = sem_init_ns(ns); if (err) goto fail_put; + err = msg_init_ns(ns); + if (err) + goto fail_destroy_sem; + err = shm_init_ns(ns); + if (err) + goto fail_destroy_msg; - sem_init_ns(ns); - msg_init_ns(ns); - shm_init_ns(ns); + err = mq_init_ns(ns); + if (err) + goto fail_destroy_shm; return ns; +fail_destroy_shm: + shm_exit_ns(ns); +fail_destroy_msg: + msg_exit_ns(ns); +fail_destroy_sem: + sem_exit_ns(ns); fail_put: put_user_ns(ns->user_ns); ns_free_inum(&ns->ns); @@ -144,7 +156,7 @@ static void free_ipc_ns(struct ipc_namespace *ns) */ void put_ipc_ns(struct ipc_namespace *ns) { - if (atomic_dec_and_lock(&ns->count, &mq_lock)) { + if (refcount_dec_and_lock(&ns->count, &mq_lock)) { mq_clear_sbinfo(ns); spin_unlock(&mq_lock); mq_put_mnt(ns); @@ -122,7 +122,7 @@ struct sem_undo { * that may be shared among all a CLONE_SYSVSEM task group. */ struct sem_undo_list { - atomic_t refcnt; + refcount_t refcnt; spinlock_t lock; struct list_head list_proc; }; @@ -130,8 +130,6 @@ struct sem_undo_list { #define sem_ids(ns) ((ns)->ids[IPC_SEM_IDS]) -#define sem_checkid(sma, semid) ipc_checkid(&sma->sem_perm, semid) - static int newary(struct ipc_namespace *, struct ipc_params *); static void freeary(struct ipc_namespace *, struct kern_ipc_perm *); #ifdef CONFIG_PROC_FS @@ -185,14 +183,14 @@ static int sysvipc_sem_proc_show(struct seq_file *s, void *it); #define sc_semopm sem_ctls[2] #define sc_semmni sem_ctls[3] -void sem_init_ns(struct ipc_namespace *ns) +int 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]); + return ipc_init_ids(&ns->ids[IPC_SEM_IDS]); } #ifdef CONFIG_IPC_NS @@ -200,15 +198,18 @@ 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) +int __init sem_init(void) { - sem_init_ns(&init_ipc_ns); + const int err = 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); + return err; } /** @@ -511,7 +512,7 @@ static int newary(struct ipc_namespace *ns, struct ipc_params *params) INIT_LIST_HEAD(&sma->pending_const); INIT_LIST_HEAD(&sma->list_id); sma->sem_nsems = nsems; - sma->sem_ctime = get_seconds(); + sma->sem_ctime = ktime_get_real_seconds(); retval = ipc_addid(&sem_ids(ns), &sma->sem_perm, ns->sc_semmni); if (retval < 0) { @@ -1162,14 +1163,14 @@ static unsigned long copy_semid_to_user(void __user *buf, struct semid64_ds *in, } } -static time_t get_semotime(struct sem_array *sma) +static time64_t get_semotime(struct sem_array *sma) { int i; - time_t res; + time64_t res; res = sma->sems[0].sem_otime; for (i = 1; i < sma->sem_nsems; i++) { - time_t to = sma->sems[i].sem_otime; + time64_t to = sma->sems[i].sem_otime; if (to > res) res = to; @@ -1177,112 +1178,95 @@ static time_t get_semotime(struct sem_array *sma) return res; } -static int semctl_nolock(struct ipc_namespace *ns, int semid, - int cmd, int version, void __user *p) +static int semctl_stat(struct ipc_namespace *ns, int semid, + int cmd, struct semid64_ds *semid64) { - int err; struct sem_array *sma; + int id = 0; + int err; - switch (cmd) { - case IPC_INFO: - case SEM_INFO: - { - struct seminfo seminfo; - int max_id; - - err = security_sem_semctl(NULL, cmd); - if (err) - return err; + memset(semid64, 0, sizeof(*semid64)); - 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; + rcu_read_lock(); + if (cmd == SEM_STAT) { + sma = sem_obtain_object(ns, semid); + if (IS_ERR(sma)) { + err = PTR_ERR(sma); + goto out_unlock; + } + id = sma->sem_perm.id; + } else { + sma = sem_obtain_object_check(ns, semid); + if (IS_ERR(sma)) { + err = PTR_ERR(sma); + goto out_unlock; } - max_id = ipc_get_maxid(&sem_ids(ns)); - up_read(&sem_ids(ns).rwsem); - if (copy_to_user(p, &seminfo, sizeof(struct seminfo))) - return -EFAULT; - return (max_id < 0) ? 0 : max_id; } - case IPC_STAT: - case SEM_STAT: - { - struct semid64_ds tbuf; - int id = 0; - - memset(&tbuf, 0, sizeof(tbuf)); - rcu_read_lock(); - if (cmd == SEM_STAT) { - sma = sem_obtain_object(ns, semid); - if (IS_ERR(sma)) { - err = PTR_ERR(sma); - goto out_unlock; - } - id = sma->sem_perm.id; - } else { - sma = sem_obtain_object_check(ns, semid); - if (IS_ERR(sma)) { - err = PTR_ERR(sma); - goto out_unlock; - } - } + err = -EACCES; + if (ipcperms(ns, &sma->sem_perm, S_IRUGO)) + goto out_unlock; - err = -EACCES; - if (ipcperms(ns, &sma->sem_perm, S_IRUGO)) - goto out_unlock; + err = security_sem_semctl(sma, cmd); + if (err) + goto out_unlock; - err = security_sem_semctl(sma, cmd); - if (err) - goto out_unlock; + kernel_to_ipc64_perm(&sma->sem_perm, &semid64->sem_perm); + semid64->sem_otime = get_semotime(sma); + semid64->sem_ctime = sma->sem_ctime; + semid64->sem_nsems = sma->sem_nsems; + rcu_read_unlock(); + return id; - kernel_to_ipc64_perm(&sma->sem_perm, &tbuf.sem_perm); - tbuf.sem_otime = get_semotime(sma); - tbuf.sem_ctime = sma->sem_ctime; - tbuf.sem_nsems = sma->sem_nsems; - rcu_read_unlock(); - if (copy_semid_to_user(p, &tbuf, version)) - return -EFAULT; - return id; - } - default: - return -EINVAL; - } 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_id; + 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_id = ipc_get_maxid(&sem_ids(ns)); + up_read(&sem_ids(ns).rwsem); + if (copy_to_user(p, &seminfo, sizeof(struct seminfo))) + return -EFAULT; + return (max_id < 0) ? 0 : max_id; +} + static int semctl_setval(struct ipc_namespace *ns, int semid, int semnum, - unsigned long arg) + int val) { struct sem_undo *un; struct sem_array *sma; struct sem *curr; - int err, val; + int err; DEFINE_WAKE_Q(wake_q); -#if defined(CONFIG_64BIT) && defined(__BIG_ENDIAN) - /* big-endian 64bit */ - val = arg >> 32; -#else - /* 32bit or little-endian 64bit */ - val = arg; -#endif - if (val > SEMVMX || val < 0) return -ERANGE; @@ -1326,7 +1310,7 @@ static int semctl_setval(struct ipc_namespace *ns, int semid, int semnum, curr->semval = val; curr->sempid = task_tgid_vnr(current); - sma->sem_ctime = get_seconds(); + 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); @@ -1454,7 +1438,7 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, for (i = 0; i < nsems; i++) un->semadj[i] = 0; } - sma->sem_ctime = get_seconds(); + 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; @@ -1531,23 +1515,17 @@ copy_semid_from_user(struct semid64_ds *out, void __user *buf, int version) * 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, int version, void __user *p) + int cmd, struct semid64_ds *semid64) { struct sem_array *sma; int err; - struct semid64_ds semid64; struct kern_ipc_perm *ipcp; - if (cmd == IPC_SET) { - if (copy_semid_from_user(&semid64, p, version)) - return -EFAULT; - } - down_write(&sem_ids(ns).rwsem); rcu_read_lock(); ipcp = ipcctl_pre_down_nolock(ns, &sem_ids(ns), semid, cmd, - &semid64.sem_perm, 0); + &semid64->sem_perm, 0); if (IS_ERR(ipcp)) { err = PTR_ERR(ipcp); goto out_unlock1; @@ -1567,10 +1545,10 @@ static int semctl_down(struct ipc_namespace *ns, int semid, goto out_up; case IPC_SET: sem_lock(sma, NULL, -1); - err = ipc_update_perm(&semid64.sem_perm, ipcp); + err = ipc_update_perm(&semid64->sem_perm, ipcp); if (err) goto out_unlock0; - sma->sem_ctime = get_seconds(); + sma->sem_ctime = ktime_get_real_seconds(); break; default: err = -EINVAL; @@ -1591,6 +1569,8 @@ SYSCALL_DEFINE4(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; @@ -1601,9 +1581,15 @@ SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, unsigned long, arg) switch (cmd) { case IPC_INFO: case SEM_INFO: + return semctl_info(ns, semid, cmd, p); case IPC_STAT: case SEM_STAT: - return semctl_nolock(ns, semid, cmd, version, p); + 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: @@ -1611,15 +1597,120 @@ SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, unsigned long, arg) 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; + case IPC_RMID: + return semctl_down(ns, semid, cmd, &semid64); + default: + return -EINVAL; + } +} + +#ifdef CONFIG_COMPAT + +struct compat_semid_ds { + struct compat_ipc_perm sem_perm; + compat_time_t sem_otime; + compat_time_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 *p = buf; + return get_compat_ipc64_perm(&out->sem_perm, &p->sem_perm); + } else { + struct compat_semid_ds *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 = in->sem_otime; + v.sem_ctime = 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)); + } +} + +COMPAT_SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, int, arg) +{ + void __user *p = compat_ptr(arg); + struct ipc_namespace *ns; + struct semid64_ds semid64; + int version = compat_ipc_parse_version(&cmd); + 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: + 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_RMID: case IPC_SET: - return semctl_down(ns, semid, cmd, version, p); + if (copy_compat_semid_from_user(&semid64, p, version)) + return -EFAULT; + /* fallthru */ + case IPC_RMID: + return semctl_down(ns, semid, cmd, &semid64); default: return -EINVAL; } } +#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, @@ -1642,7 +1733,7 @@ static inline int get_undo_list(struct sem_undo_list **undo_listp) if (undo_list == NULL) return -ENOMEM; spin_lock_init(&undo_list->lock); - atomic_set(&undo_list->refcnt, 1); + refcount_set(&undo_list->refcnt, 1); INIT_LIST_HEAD(&undo_list->list_proc); current->sysvsem.undo_list = undo_list; @@ -1765,8 +1856,8 @@ out: return un; } -SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops, - unsigned, nsops, const struct timespec __user *, timeout) +static long do_semtimedop(int semid, struct sembuf __user *tsops, + unsigned nsops, const struct timespec64 *timeout) { int error = -EINVAL; struct sem_array *sma; @@ -1786,7 +1877,7 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops, if (nsops > ns->sc_semopm) return -E2BIG; if (nsops > SEMOPM_FAST) { - sops = kmalloc(sizeof(*sops)*nsops, GFP_KERNEL); + sops = kvmalloc(sizeof(*sops)*nsops, GFP_KERNEL); if (sops == NULL) return -ENOMEM; } @@ -1797,17 +1888,12 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops, } if (timeout) { - struct timespec _timeout; - if (copy_from_user(&_timeout, timeout, sizeof(*timeout))) { - error = -EFAULT; - goto out_free; - } - if (_timeout.tv_sec < 0 || _timeout.tv_nsec < 0 || - _timeout.tv_nsec >= 1000000000L) { + if (timeout->tv_sec < 0 || timeout->tv_nsec < 0 || + timeout->tv_nsec >= 1000000000L) { error = -EINVAL; goto out_free; } - jiffies_left = timespec_to_jiffies(&_timeout); + jiffies_left = timespec64_to_jiffies(timeout); } max = 0; @@ -2018,14 +2104,41 @@ out_unlock_free: rcu_read_unlock(); out_free: if (sops != fast_sops) - kfree(sops); + kvfree(sops); return error; } +SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops, + unsigned, nsops, const struct 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); +} + +#ifdef CONFIG_COMPAT +COMPAT_SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsems, + unsigned, nsops, + const struct compat_timespec __user *, timeout) +{ + if (timeout) { + struct timespec64 ts; + if (compat_get_timespec64(&ts, timeout)) + return -EFAULT; + return do_semtimedop(semid, tsems, nsops, &ts); + } + return do_semtimedop(semid, tsems, nsops, NULL); +} +#endif + SYSCALL_DEFINE3(semop, int, semid, struct sembuf __user *, tsops, unsigned, nsops) { - return sys_semtimedop(semid, tsops, nsops, NULL); + return do_semtimedop(semid, tsops, nsops, NULL); } /* If CLONE_SYSVSEM is set, establish sharing of SEM_UNDO state between @@ -2041,7 +2154,7 @@ int copy_semundo(unsigned long clone_flags, struct task_struct *tsk) error = get_undo_list(&undo_list); if (error) return error; - atomic_inc(&undo_list->refcnt); + refcount_inc(&undo_list->refcnt); tsk->sysvsem.undo_list = undo_list; } else tsk->sysvsem.undo_list = NULL; @@ -2070,7 +2183,7 @@ void exit_sem(struct task_struct *tsk) return; tsk->sysvsem.undo_list = NULL; - if (!atomic_dec_and_test(&ulp->refcnt)) + if (!refcount_dec_and_test(&ulp->refcnt)) return; for (;;) { @@ -2091,7 +2204,8 @@ void exit_sem(struct task_struct *tsk) * possibility where we exit while freeary() didn't * finish unlocking sem_undo_list. */ - spin_unlock_wait(&ulp->lock); + spin_lock(&ulp->lock); + spin_unlock(&ulp->lock); rcu_read_unlock(); break; } @@ -2181,7 +2295,7 @@ 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); - time_t sem_otime; + time64_t sem_otime; /* * The proc interface isn't aware of sem_lock(), it calls @@ -2194,7 +2308,7 @@ static int sysvipc_sem_proc_show(struct seq_file *s, void *it) sem_otime = get_semotime(sma); seq_printf(s, - "%10d %10d %4o %10u %5u %5u %5u %5u %10lu %10lu\n", + "%10d %10d %4o %10u %5u %5u %5u %5u %10llu %10llu\n", sma->sem_perm.key, sma->sem_perm.id, sma->sem_perm.mode, @@ -72,14 +72,14 @@ static void shm_destroy(struct ipc_namespace *ns, struct shmid_kernel *shp); static int sysvipc_shm_proc_show(struct seq_file *s, void *it); #endif -void shm_init_ns(struct ipc_namespace *ns) +int 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)); + return ipc_init_ids(&shm_ids(ns)); } /* @@ -95,7 +95,7 @@ static void do_shm_rmid(struct ipc_namespace *ns, struct kern_ipc_perm *ipcp) if (shp->shm_nattch) { shp->shm_perm.mode |= SHM_DEST; /* Do not find it any more */ - shp->shm_perm.key = IPC_PRIVATE; + ipc_set_key_private(&shm_ids(ns), &shp->shm_perm); shm_unlock(shp); } else shm_destroy(ns, shp); @@ -106,13 +106,15 @@ 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); - return 0; + const int err = shm_init_ns(&init_ipc_ns); + WARN(err, "ipc: sysv shm_init_ns failed: %d\n", err); + return err; } pure_initcall(ipc_ns_init); @@ -200,7 +202,7 @@ static int __shm_open(struct vm_area_struct *vma) if (IS_ERR(shp)) return PTR_ERR(shp); - shp->shm_atim = get_seconds(); + shp->shm_atim = ktime_get_real_seconds(); shp->shm_lprid = task_tgid_vnr(current); shp->shm_nattch++; shm_unlock(shp); @@ -287,7 +289,7 @@ static void shm_close(struct vm_area_struct *vma) goto done; /* no-op */ shp->shm_lprid = task_tgid_vnr(current); - shp->shm_dtim = get_seconds(); + shp->shm_dtim = ktime_get_real_seconds(); shp->shm_nattch--; if (shm_may_destroy(ns, shp)) shm_destroy(ns, shp); @@ -592,7 +594,7 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params) shp->shm_cprid = task_tgid_vnr(current); shp->shm_lprid = 0; shp->shm_atim = shp->shm_dtim = 0; - shp->shm_ctim = get_seconds(); + shp->shm_ctim = ktime_get_real_seconds(); shp->shm_segsz = size; shp->shm_nattch = 0; shp->shm_file = file; @@ -813,23 +815,17 @@ static void shm_get_stat(struct ipc_namespace *ns, unsigned long *rss, * 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 shmid_ds __user *buf, int version) + struct shmid64_ds *shmid64) { struct kern_ipc_perm *ipcp; - struct shmid64_ds shmid64; struct shmid_kernel *shp; int err; - if (cmd == IPC_SET) { - if (copy_shmid_from_user(&shmid64, buf, version)) - return -EFAULT; - } - down_write(&shm_ids(ns).rwsem); rcu_read_lock(); ipcp = ipcctl_pre_down_nolock(ns, &shm_ids(ns), shmid, cmd, - &shmid64.shm_perm, 0); + &shmid64->shm_perm, 0); if (IS_ERR(ipcp)) { err = PTR_ERR(ipcp); goto out_unlock1; @@ -849,10 +845,10 @@ static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd, goto out_up; case IPC_SET: ipc_lock_object(&shp->shm_perm); - err = ipc_update_perm(&shmid64.shm_perm, ipcp); + err = ipc_update_perm(&shmid64->shm_perm, ipcp); if (err) goto out_unlock0; - shp->shm_ctim = get_seconds(); + shp->shm_ctim = ktime_get_real_seconds(); break; default: err = -EINVAL; @@ -868,125 +864,175 @@ out_up: return err; } -static int shmctl_nolock(struct ipc_namespace *ns, int shmid, - int cmd, int version, void __user *buf) +static int shmctl_ipc_info(struct ipc_namespace *ns, + struct shminfo64 *shminfo) { - int err; - struct shmid_kernel *shp; - - /* preliminary security checks for *_INFO */ - if (cmd == IPC_INFO || cmd == SHM_INFO) { - err = security_shm_shmctl(NULL, cmd); - if (err) - return err; - } - - switch (cmd) { - case IPC_INFO: - { - struct shminfo64 shminfo; - - 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; - if (copy_shminfo_to_user(buf, &shminfo, version)) - return -EFAULT; - + 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_maxid(&shm_ids(ns)); up_read(&shm_ids(ns).rwsem); - if (err < 0) err = 0; - goto out; } - case SHM_INFO: - { - struct shm_info shm_info; + return err; +} - memset(&shm_info, 0, sizeof(shm_info)); +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; + 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_maxid(&shm_ids(ns)); up_read(&shm_ids(ns).rwsem); - if (copy_to_user(buf, &shm_info, sizeof(shm_info))) { - err = -EFAULT; - goto out; + 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 result; + int err; + + rcu_read_lock(); + if (cmd == SHM_STAT) { + shp = shm_obtain_object(ns, shmid); + if (IS_ERR(shp)) { + err = PTR_ERR(shp); + goto out_unlock; + } + result = shp->shm_perm.id; + } else { + shp = shm_obtain_object_check(ns, shmid); + if (IS_ERR(shp)) { + err = PTR_ERR(shp); + goto out_unlock; } + result = 0; + } - err = err < 0 ? 0 : err; - goto out; + err = -EACCES; + if (ipcperms(ns, &shp->shm_perm, S_IRUGO)) + goto out_unlock; + + err = security_shm_shmctl(shp, cmd); + if (err) + goto out_unlock; + + memset(tbuf, 0, sizeof(*tbuf)); + 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; + tbuf->shm_cpid = shp->shm_cprid; + tbuf->shm_lpid = shp->shm_lprid; + tbuf->shm_nattch = shp->shm_nattch; + rcu_read_unlock(); + return result; + +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; } - case SHM_STAT: - case IPC_STAT: - { - struct shmid64_ds tbuf; - int result; - - rcu_read_lock(); - if (cmd == SHM_STAT) { - shp = shm_obtain_object(ns, shmid); - if (IS_ERR(shp)) { - err = PTR_ERR(shp); - goto out_unlock; - } - result = shp->shm_perm.id; - } else { - shp = shm_obtain_object_check(ns, shmid); - if (IS_ERR(shp)) { - err = PTR_ERR(shp); - goto out_unlock; - } - result = 0; - } - err = -EACCES; - if (ipcperms(ns, &shp->shm_perm, S_IRUGO)) - goto out_unlock; + audit_ipc_obj(&(shp->shm_perm)); + err = security_shm_shmctl(shp, cmd); + if (err) + goto out_unlock1; - err = security_shm_shmctl(shp, cmd); - if (err) - goto out_unlock; + ipc_lock_object(&shp->shm_perm); - memset(&tbuf, 0, sizeof(tbuf)); - 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; - tbuf.shm_cpid = shp->shm_cprid; - tbuf.shm_lpid = shp->shm_lprid; - tbuf.shm_nattch = shp->shm_nattch; - rcu_read_unlock(); - - if (copy_shmid_to_user(buf, &tbuf, version)) - err = -EFAULT; - else - err = result; - goto out; + /* check if shm_destroy() is tearing down shp */ + if (!ipc_valid_object(&shp->shm_perm)) { + err = -EIDRM; + goto out_unlock0; } - default: - return -EINVAL; + + 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; + } } -out_unlock: + shm_file = shp->shm_file; + if (is_file_hugepages(shm_file)) + goto out_unlock0; + + if (cmd == SHM_LOCK) { + struct user_struct *user = current_user(); + + err = shmem_lock(shm_file, 1, user); + if (!err && !(shp->shm_perm.mode & SHM_LOCKED)) { + shp->shm_perm.mode |= SHM_LOCKED; + shp->mlock_user = user; + } + goto out_unlock0; + } + + /* SHM_UNLOCK */ + if (!(shp->shm_perm.mode & SHM_LOCKED)) + goto out_unlock0; + shmem_lock(shm_file, 0, shp->mlock_user); + shp->shm_perm.mode &= ~SHM_LOCKED; + shp->mlock_user = 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(); -out: return err; } SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf) { - struct shmid_kernel *shp; int err, version; struct ipc_namespace *ns; + struct shmid64_ds sem64; if (cmd < 0 || shmid < 0) return -EINVAL; @@ -995,92 +1041,222 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf) ns = current->nsproxy->ipc_ns; switch (cmd) { - case IPC_INFO: - case SHM_INFO: + 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 IPC_STAT: - return shmctl_nolock(ns, shmid, cmd, version, buf); - case IPC_RMID: + 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: - return shmctl_down(ns, shmid, cmd, buf, version); + if (copy_shmid_from_user(&sem64, buf, version)) + return -EFAULT; + /* fallthru */ + case IPC_RMID: + return shmctl_down(ns, shmid, cmd, &sem64); case SHM_LOCK: case SHM_UNLOCK: - { - struct file *shm_file; + return shmctl_do_lock(ns, shmid, cmd); + default: + return -EINVAL; + } +} - rcu_read_lock(); - shp = shm_obtain_object_check(ns, shmid); - if (IS_ERR(shp)) { - err = PTR_ERR(shp); - goto out_unlock1; - } +#ifdef CONFIG_COMPAT + +struct compat_shmid_ds { + struct compat_ipc_perm shm_perm; + int shm_segsz; + compat_time_t shm_atime; + compat_time_t shm_dtime; + compat_time_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; +}; - audit_ipc_obj(&(shp->shm_perm)); - err = security_shm_shmctl(shp, cmd); - if (err) - goto out_unlock1; +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; +}; - ipc_lock_object(&shp->shm_perm); +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; +}; - /* check if shm_destroy() is tearing down shp */ - if (!ipc_valid_object(&shp->shm_perm)) { - err = -EIDRM; - goto out_unlock0; - } +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)); + } +} - 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; - } - } +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)); +} - shm_file = shp->shm_file; - if (is_file_hugepages(shm_file)) - goto out_unlock0; +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 = 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)); + } 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 *p = buf; + return get_compat_ipc64_perm(&out->shm_perm, &p->shm_perm); + } else { + struct compat_shmid_ds *p = buf; + return get_compat_ipc_perm(&out->shm_perm, &p->shm_perm); + } +} - if (cmd == SHM_LOCK) { - struct user_struct *user = current_user(); +COMPAT_SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, void __user *, uptr) +{ + struct ipc_namespace *ns; + struct shmid64_ds sem64; + int version = compat_ipc_parse_version(&cmd); + int err; - err = shmem_lock(shm_file, 1, user); - if (!err && !(shp->shm_perm.mode & SHM_LOCKED)) { - shp->shm_perm.mode |= SHM_LOCKED; - shp->mlock_user = user; - } - goto out_unlock0; - } + ns = current->nsproxy->ipc_ns; - /* SHM_UNLOCK */ - if (!(shp->shm_perm.mode & SHM_LOCKED)) - goto out_unlock0; - shmem_lock(shm_file, 0, shp->mlock_user); - shp->shm_perm.mode &= ~SHM_LOCKED; - shp->mlock_user = NULL; - get_file(shm_file); - ipc_unlock_object(&shp->shm_perm); - rcu_read_unlock(); - shmem_unlock_mapping(shm_file->f_mapping); + if (cmd < 0 || shmid < 0) + return -EINVAL; - fput(shm_file); + 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: + 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; + /* fallthru */ + case IPC_RMID: + return shmctl_down(ns, shmid, cmd, &sem64); + case SHM_LOCK: + case SHM_UNLOCK: + return shmctl_do_lock(ns, shmid, cmd); + break; default: return -EINVAL; } - -out_unlock0: - ipc_unlock_object(&shp->shm_perm); -out_unlock1: - rcu_read_unlock(); return err; } +#endif /* * Fix shmaddr, allocate descriptor, map shm, add attach descriptor to lists. @@ -1265,6 +1441,25 @@ SYSCALL_DEFINE3(shmat, int, shmid, char __user *, shmaddr, int, shmflg) 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. @@ -1395,7 +1590,7 @@ static int sysvipc_shm_proc_show(struct seq_file *s, void *it) seq_printf(s, "%10d %10d %4o " SIZE_SPEC " %5u %5u " - "%5lu %5u %5u %5u %5u %10lu %10lu %10lu " + "%5lu %5u %5u %5u %5u %10llu %10llu %10llu " SIZE_SPEC " " SIZE_SPEC "\n", shp->shm_perm.key, shp->shm_perm.id, diff --git a/ipc/syscall.c b/ipc/syscall.c index 5242948..6670227 100644 --- a/ipc/syscall.c +++ b/ipc/syscall.c @@ -5,12 +5,12 @@ * the individual syscalls instead. */ #include <linux/unistd.h> +#include <linux/syscalls.h> #ifdef __ARCH_WANT_SYS_IPC #include <linux/errno.h> #include <linux/ipc.h> #include <linux/shm.h> -#include <linux/syscalls.h> #include <linux/uaccess.h> SYSCALL_DEFINE6(ipc, unsigned int, call, int, first, unsigned long, second, @@ -97,3 +97,91 @@ SYSCALL_DEFINE6(ipc, unsigned int, call, int, first, unsigned long, second, } } #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 +COMPAT_SYSCALL_DEFINE6(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 sys_semtimedop(first, compat_ptr(ptr), second, NULL); + case SEMTIMEDOP: + return compat_sys_semtimedop(first, compat_ptr(ptr), second, + compat_ptr(fifth)); + case SEMGET: + return sys_semget(first, second, third); + case SEMCTL: + if (!ptr) + return -EINVAL; + if (get_user(pad, (u32 __user *) compat_ptr(ptr))) + return -EFAULT; + return compat_sys_semctl(first, second, third, pad); + + case MSGSND: + return compat_sys_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_sys_msgrcv(first, ipck.msgp, second, + ipck.msgtyp, third); + } + return compat_sys_msgrcv(first, ptr, second, fifth, third); + } + case MSGGET: + return sys_msgget(first, second); + case MSGCTL: + return compat_sys_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 *)compat_ptr(third)); + } + case SHMDT: + return sys_shmdt(compat_ptr(ptr)); + case SHMGET: + return sys_shmget(first, (unsigned)second, third); + case SHMCTL: + return compat_sys_shmctl(first, second, compat_ptr(ptr)); + } + + return -ENOSYS; +} +#endif +#endif @@ -83,27 +83,46 @@ struct ipc_proc_iface { */ static int __init ipc_init(void) { - sem_init(); - msg_init(); + int err_sem, err_msg; + + err_sem = sem_init(); + WARN(err_sem, "ipc: sysv sem_init failed: %d\n", err_sem); + err_msg = msg_init(); + WARN(err_msg, "ipc: sysv msg_init failed: %d\n", err_msg); shm_init(); - return 0; + + return err_msg ? err_msg : err_sem; } 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 = FIELD_SIZEOF(struct kern_ipc_perm, key), + .locks_mul = 1, + .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 IPCMNI) then initialise the ids idr. + * below IPCMNI) then initialise the keys hashtable and ids idr. */ -void ipc_init_ids(struct ipc_ids *ids) +int ipc_init_ids(struct ipc_ids *ids) { + int err; ids->in_use = 0; ids->seq = 0; ids->next_id = -1; init_rwsem(&ids->rwsem); + err = rhashtable_init(&ids->key_ht, &ipc_kht_params); + if (err) + return err; idr_init(&ids->ipcs_idr); + ids->tables_initialized = true; + return 0; } #ifdef CONFIG_PROC_FS @@ -147,28 +166,20 @@ void __init ipc_init_proc_interface(const char *path, const char *header, * 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 ipc_ids.rwsem held. + * 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 *ipc; - int next_id; - int total; - - for (total = 0, next_id = 0; total < ids->in_use; next_id++) { - ipc = idr_find(&ids->ipcs_idr, next_id); - - if (ipc == NULL) - continue; + struct kern_ipc_perm *ipcp = NULL; - if (ipc->key != key) { - total++; - continue; - } + if (likely(ids->tables_initialized)) + ipcp = rhashtable_lookup_fast(&ids->key_ht, &key, + ipc_kht_params); + if (ipcp) { rcu_read_lock(); - ipc_lock_object(ipc); - return ipc; + ipc_lock_object(ipcp); + return ipcp; } return NULL; @@ -221,18 +232,18 @@ int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int size) { kuid_t euid; kgid_t egid; - int id; + int id, err; int next_id = ids->next_id; if (size > IPCMNI) size = IPCMNI; - if (ids->in_use >= size) + if (!ids->tables_initialized || ids->in_use >= size) return -ENOSPC; idr_preload(GFP_KERNEL); - atomic_set(&new->refcount, 1); + refcount_set(&new->refcount, 1); spin_lock_init(&new->lock); new->deleted = false; rcu_read_lock(); @@ -246,6 +257,15 @@ int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int size) (next_id < 0) ? 0 : ipcid_to_idx(next_id), 0, GFP_NOWAIT); idr_preload_end(); + + if (id >= 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, id); + id = err; + } + } if (id < 0) { spin_unlock(&new->lock); rcu_read_unlock(); @@ -377,6 +397,20 @@ static int ipcget_public(struct ipc_namespace *ns, struct ipc_ids *ids, 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) + rhashtable_remove_fast(&ids->key_ht, &ipcp->khtnode, + ipc_kht_params); +} /** * ipc_rmid - remove an ipc identifier @@ -391,19 +425,34 @@ void ipc_rmid(struct ipc_ids *ids, struct kern_ipc_perm *ipcp) int lid = ipcid_to_idx(ipcp->id); idr_remove(&ids->ipcs_idr, lid); + ipc_kht_remove(ids, ipcp); ids->in_use--; ipcp->deleted = true; } +/** + * 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; +} + int ipc_rcu_getref(struct kern_ipc_perm *ptr) { - return atomic_inc_not_zero(&ptr->refcount); + return refcount_inc_not_zero(&ptr->refcount); } void ipc_rcu_putref(struct kern_ipc_perm *ptr, void (*func)(struct rcu_head *head)) { - if (!atomic_dec_and_test(&ptr->refcount)) + if (!refcount_dec_and_test(&ptr->refcount)) return; call_rcu(&ptr->rcu, func); @@ -485,7 +534,7 @@ void ipc64_perm_to_ipc_perm(struct ipc64_perm *in, struct ipc_perm *out) } /** - * ipc_obtain_object + * ipc_obtain_object_idr * @ids: ipc identifier set * @id: ipc id to look for * @@ -499,6 +548,9 @@ struct kern_ipc_perm *ipc_obtain_object_idr(struct ipc_ids *ids, int id) struct kern_ipc_perm *out; int lid = ipcid_to_idx(id); + if (unlikely(!ids->tables_initialized)) + return ERR_PTR(-EINVAL); + out = idr_find(&ids->ipcs_idr, lid); if (!out) return ERR_PTR(-EINVAL); @@ -15,8 +15,8 @@ #define SEQ_MULTIPLIER (IPCMNI) -void sem_init(void); -void msg_init(void); +int sem_init(void); +int msg_init(void); void shm_init(void); struct ipc_namespace; @@ -30,17 +30,17 @@ static inline void mq_put_mnt(struct ipc_namespace *ns) { } #endif #ifdef CONFIG_SYSVIPC -void sem_init_ns(struct ipc_namespace *ns); -void msg_init_ns(struct ipc_namespace *ns); -void shm_init_ns(struct ipc_namespace *ns); +int sem_init_ns(struct ipc_namespace *ns); +int msg_init_ns(struct ipc_namespace *ns); +int 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 void msg_init_ns(struct ipc_namespace *ns) { } -static inline void shm_init_ns(struct ipc_namespace *ns) { } +static inline int sem_init_ns(struct ipc_namespace *ns) { return 0; } +static inline int msg_init_ns(struct ipc_namespace *ns) { return 0; } +static inline int shm_init_ns(struct ipc_namespace *ns) { return 0; } static inline void sem_exit_ns(struct ipc_namespace *ns) { } static inline void msg_exit_ns(struct ipc_namespace *ns) { } @@ -79,7 +79,7 @@ struct ipc_ops { struct seq_file; struct ipc_ids; -void ipc_init_ids(struct ipc_ids *); +int ipc_init_ids(struct ipc_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 *)); @@ -104,6 +104,9 @@ int ipc_get_maxid(struct ipc_ids *); /* 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); @@ -191,4 +194,34 @@ 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 *)); + +#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) +{ +#ifdef CONFIG_ARCH_WANT_COMPAT_IPC_PARSE_VERSION + int version = *cmd & IPC_64; + *cmd &= ~IPC_64; + return version; +#else + return IPC_64; +#endif +} +#endif #endif |