diff options
Diffstat (limited to 'ipc/shm.c')
-rw-r--r-- | ipc/shm.c | 533 |
1 files changed, 363 insertions, 170 deletions
diff --git a/ipc/shm.c b/ipc/shm.c index 8fc97beb5234..1b3adfe3c60e 100644 --- a/ipc/shm.c +++ b/ipc/shm.c @@ -202,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); @@ -289,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); @@ -594,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; @@ -815,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; @@ -851,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; @@ -870,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; @@ -997,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(up, &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)); + } +} - if (cmd == SHM_LOCK) { - struct user_struct *user = current_user(); +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); + } +} - 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; - } +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; - /* 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); + ns = current->nsproxy->ipc_ns; + + 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(&sem64, uptr, 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. @@ -1267,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. @@ -1397,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, |