Commit cc73fee0 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'work.ipc' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs

Pull ipc compat cleanup and 64-bit time_t from Al Viro:
 "IPC copyin/copyout sanitizing, including 64bit time_t work from Deepa
  Dinamani"

* 'work.ipc' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  utimes: Make utimes y2038 safe
  ipc: shm: Make shmid_kernel timestamps y2038 safe
  ipc: sem: Make sem_array timestamps y2038 safe
  ipc: msg: Make msg_queue timestamps y2038 safe
  ipc: mqueue: Replace timespec with timespec64
  ipc: Make sys_semtimedop() y2038 safe
  get rid of SYSVIPC_COMPAT on ia64
  semtimedop(): move compat to native
  shmat(2): move compat to native
  msgrcv(2), msgsnd(2): move compat to native
  ipc(2): move compat to native
  ipc: make use of compat ipc_perm helpers
  semctl(): move compat to native
  semctl(): separate all layout-dependent copyin/copyout
  msgctl(): move compat to native
  msgctl(): split the actual work from copyin/copyout
  ipc: move compat shmctl to native
  shmctl: split the work from copyin/copyout
parents e7cdb60f aaed2dd8
...@@ -56,9 +56,4 @@ config IA64_DEBUG_IRQ ...@@ -56,9 +56,4 @@ config IA64_DEBUG_IRQ
and restore instructions. It's useful for tracking down spinlock and restore instructions. It's useful for tracking down spinlock
problems, but slow! If you're unsure, select N. problems, but slow! If you're unsure, select N.
config SYSVIPC_COMPAT
bool
depends on COMPAT && SYSVIPC
default y
endmenu endmenu
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
*/ */
SYSCALL_DEFINE2(utime, char __user *, filename, struct utimbuf __user *, times) SYSCALL_DEFINE2(utime, char __user *, filename, struct utimbuf __user *, times)
{ {
struct timespec tv[2]; struct timespec64 tv[2];
if (times) { if (times) {
if (get_user(tv[0].tv_sec, &times->actime) || if (get_user(tv[0].tv_sec, &times->actime) ||
...@@ -44,7 +44,7 @@ static bool nsec_valid(long nsec) ...@@ -44,7 +44,7 @@ static bool nsec_valid(long nsec)
return nsec >= 0 && nsec <= 999999999; return nsec >= 0 && nsec <= 999999999;
} }
static int utimes_common(const struct path *path, struct timespec *times) static int utimes_common(const struct path *path, struct timespec64 *times)
{ {
int error; int error;
struct iattr newattrs; struct iattr newattrs;
...@@ -115,7 +115,7 @@ static int utimes_common(const struct path *path, struct timespec *times) ...@@ -115,7 +115,7 @@ static int utimes_common(const struct path *path, struct timespec *times)
* must be owner or have write permission. * must be owner or have write permission.
* Else, update from *times, must be owner or super user. * Else, update from *times, must be owner or super user.
*/ */
long do_utimes(int dfd, const char __user *filename, struct timespec *times, long do_utimes(int dfd, const char __user *filename, struct timespec64 *times,
int flags) int flags)
{ {
int error = -EINVAL; int error = -EINVAL;
...@@ -167,10 +167,11 @@ long do_utimes(int dfd, const char __user *filename, struct timespec *times, ...@@ -167,10 +167,11 @@ long do_utimes(int dfd, const char __user *filename, struct timespec *times,
SYSCALL_DEFINE4(utimensat, int, dfd, const char __user *, filename, SYSCALL_DEFINE4(utimensat, int, dfd, const char __user *, filename,
struct timespec __user *, utimes, int, flags) struct timespec __user *, utimes, int, flags)
{ {
struct timespec tstimes[2]; struct timespec64 tstimes[2];
if (utimes) { if (utimes) {
if (copy_from_user(&tstimes, utimes, sizeof(tstimes))) if ((get_timespec64(&tstimes[0], &utimes[0]) ||
get_timespec64(&tstimes[1], &utimes[1])))
return -EFAULT; return -EFAULT;
/* Nothing to do, we must not even check the path. */ /* Nothing to do, we must not even check the path. */
...@@ -186,7 +187,7 @@ SYSCALL_DEFINE3(futimesat, int, dfd, const char __user *, filename, ...@@ -186,7 +187,7 @@ SYSCALL_DEFINE3(futimesat, int, dfd, const char __user *, filename,
struct timeval __user *, utimes) struct timeval __user *, utimes)
{ {
struct timeval times[2]; struct timeval times[2];
struct timespec tstimes[2]; struct timespec64 tstimes[2];
if (utimes) { if (utimes) {
if (copy_from_user(&times, utimes, sizeof(times))) if (copy_from_user(&times, utimes, sizeof(times)))
...@@ -224,7 +225,7 @@ SYSCALL_DEFINE2(utimes, char __user *, filename, ...@@ -224,7 +225,7 @@ SYSCALL_DEFINE2(utimes, char __user *, filename,
COMPAT_SYSCALL_DEFINE2(utime, const char __user *, filename, COMPAT_SYSCALL_DEFINE2(utime, const char __user *, filename,
struct compat_utimbuf __user *, t) struct compat_utimbuf __user *, t)
{ {
struct timespec tv[2]; struct timespec64 tv[2];
if (t) { if (t) {
if (get_user(tv[0].tv_sec, &t->actime) || if (get_user(tv[0].tv_sec, &t->actime) ||
...@@ -238,11 +239,11 @@ COMPAT_SYSCALL_DEFINE2(utime, const char __user *, filename, ...@@ -238,11 +239,11 @@ COMPAT_SYSCALL_DEFINE2(utime, const char __user *, filename,
COMPAT_SYSCALL_DEFINE4(utimensat, unsigned int, dfd, const char __user *, filename, struct compat_timespec __user *, t, int, flags) COMPAT_SYSCALL_DEFINE4(utimensat, unsigned int, dfd, const char __user *, filename, struct compat_timespec __user *, t, int, flags)
{ {
struct timespec tv[2]; struct timespec64 tv[2];
if (t) { if (t) {
if (compat_get_timespec(&tv[0], &t[0]) || if (compat_get_timespec64(&tv[0], &t[0]) ||
compat_get_timespec(&tv[1], &t[1])) compat_get_timespec64(&tv[1], &t[1]))
return -EFAULT; return -EFAULT;
if (tv[0].tv_nsec == UTIME_OMIT && tv[1].tv_nsec == UTIME_OMIT) if (tv[0].tv_nsec == UTIME_OMIT && tv[1].tv_nsec == UTIME_OMIT)
...@@ -253,7 +254,7 @@ COMPAT_SYSCALL_DEFINE4(utimensat, unsigned int, dfd, const char __user *, filena ...@@ -253,7 +254,7 @@ COMPAT_SYSCALL_DEFINE4(utimensat, unsigned int, dfd, const char __user *, filena
COMPAT_SYSCALL_DEFINE3(futimesat, unsigned int, dfd, const char __user *, filename, struct compat_timeval __user *, t) COMPAT_SYSCALL_DEFINE3(futimesat, unsigned int, dfd, const char __user *, filename, struct compat_timeval __user *, t)
{ {
struct timespec tv[2]; struct timespec64 tv[2];
if (t) { if (t) {
if (get_user(tv[0].tv_sec, &t[0].tv_sec) || if (get_user(tv[0].tv_sec, &t[0].tv_sec) ||
......
...@@ -351,7 +351,7 @@ extern int __audit_socketcall(int nargs, unsigned long *args); ...@@ -351,7 +351,7 @@ extern int __audit_socketcall(int nargs, unsigned long *args);
extern int __audit_sockaddr(int len, void *addr); extern int __audit_sockaddr(int len, void *addr);
extern void __audit_fd_pair(int fd1, int fd2); extern void __audit_fd_pair(int fd1, int fd2);
extern void __audit_mq_open(int oflag, umode_t mode, struct mq_attr *attr); extern void __audit_mq_open(int oflag, umode_t mode, struct mq_attr *attr);
extern void __audit_mq_sendrecv(mqd_t mqdes, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout); extern void __audit_mq_sendrecv(mqd_t mqdes, size_t msg_len, unsigned int msg_prio, const struct timespec64 *abs_timeout);
extern void __audit_mq_notify(mqd_t mqdes, const struct sigevent *notification); extern void __audit_mq_notify(mqd_t mqdes, const struct sigevent *notification);
extern void __audit_mq_getsetattr(mqd_t mqdes, struct mq_attr *mqstat); extern void __audit_mq_getsetattr(mqd_t mqdes, struct mq_attr *mqstat);
extern int __audit_log_bprm_fcaps(struct linux_binprm *bprm, extern int __audit_log_bprm_fcaps(struct linux_binprm *bprm,
...@@ -412,7 +412,7 @@ static inline void audit_mq_open(int oflag, umode_t mode, struct mq_attr *attr) ...@@ -412,7 +412,7 @@ static inline void audit_mq_open(int oflag, umode_t mode, struct mq_attr *attr)
if (unlikely(!audit_dummy_context())) if (unlikely(!audit_dummy_context()))
__audit_mq_open(oflag, mode, attr); __audit_mq_open(oflag, mode, attr);
} }
static inline void audit_mq_sendrecv(mqd_t mqdes, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout) static inline void audit_mq_sendrecv(mqd_t mqdes, size_t msg_len, unsigned int msg_prio, const struct timespec64 *abs_timeout)
{ {
if (unlikely(!audit_dummy_context())) if (unlikely(!audit_dummy_context()))
__audit_mq_sendrecv(mqdes, msg_len, msg_prio, abs_timeout); __audit_mq_sendrecv(mqdes, msg_len, msg_prio, abs_timeout);
...@@ -549,7 +549,7 @@ static inline void audit_mq_open(int oflag, umode_t mode, struct mq_attr *attr) ...@@ -549,7 +549,7 @@ static inline void audit_mq_open(int oflag, umode_t mode, struct mq_attr *attr)
{ } { }
static inline void audit_mq_sendrecv(mqd_t mqdes, size_t msg_len, static inline void audit_mq_sendrecv(mqd_t mqdes, size_t msg_len,
unsigned int msg_prio, unsigned int msg_prio,
const struct timespec *abs_timeout) const struct timespec64 *abs_timeout)
{ } { }
static inline void audit_mq_notify(mqd_t mqdes, static inline void audit_mq_notify(mqd_t mqdes,
const struct sigevent *notification) const struct sigevent *notification)
......
...@@ -171,15 +171,6 @@ extern int get_compat_itimerspec64(struct itimerspec64 *its, ...@@ -171,15 +171,6 @@ extern int get_compat_itimerspec64(struct itimerspec64 *its,
extern int put_compat_itimerspec64(const struct itimerspec64 *its, extern int put_compat_itimerspec64(const struct itimerspec64 *its,
struct compat_itimerspec __user *uits); struct compat_itimerspec __user *uits);
/*
* This function convert a timespec if necessary and returns a *user
* space* pointer. If no conversion is necessary, it returns the
* initial pointer. NULL is a legitimate argument and will always
* output NULL.
*/
extern int compat_convert_timespec(struct timespec __user **,
const void __user *);
struct compat_iovec { struct compat_iovec {
compat_uptr_t iov_base; compat_uptr_t iov_base;
compat_size_t iov_len; compat_size_t iov_len;
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
#define _LINUX_MSG_H #define _LINUX_MSG_H
#include <linux/list.h> #include <linux/list.h>
#include <linux/time64.h>
#include <uapi/linux/msg.h> #include <uapi/linux/msg.h>
/* one msg_msg structure for each message */ /* one msg_msg structure for each message */
...@@ -17,9 +18,9 @@ struct msg_msg { ...@@ -17,9 +18,9 @@ struct msg_msg {
/* one msq_queue structure for each present queue on the system */ /* one msq_queue structure for each present queue on the system */
struct msg_queue { struct msg_queue {
struct kern_ipc_perm q_perm; struct kern_ipc_perm q_perm;
time_t q_stime; /* last msgsnd time */ time64_t q_stime; /* last msgsnd time */
time_t q_rtime; /* last msgrcv time */ time64_t q_rtime; /* last msgrcv time */
time_t q_ctime; /* last change time */ time64_t q_ctime; /* last change time */
unsigned long q_cbytes; /* current number of bytes on queue */ unsigned long q_cbytes; /* current number of bytes on queue */
unsigned long q_qnum; /* number of messages in queue */ unsigned long q_qnum; /* number of messages in queue */
unsigned long q_qbytes; /* max number of bytes on queue */ unsigned long q_qbytes; /* max number of bytes on queue */
...@@ -31,12 +32,4 @@ struct msg_queue { ...@@ -31,12 +32,4 @@ struct msg_queue {
struct list_head q_senders; struct list_head q_senders;
} __randomize_layout; } __randomize_layout;
/* Helper routines for sys_msgsnd and sys_msgrcv */
extern long do_msgsnd(int msqid, long mtype, void __user *mtext,
size_t msgsz, int msgflg);
extern long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp,
int msgflg,
long (*msg_fill)(void __user *, struct msg_msg *,
size_t));
#endif /* _LINUX_MSG_H */ #endif /* _LINUX_MSG_H */
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include <linux/atomic.h> #include <linux/atomic.h>
#include <linux/rcupdate.h> #include <linux/rcupdate.h>
#include <linux/cache.h> #include <linux/cache.h>
#include <linux/time64.h>
#include <uapi/linux/sem.h> #include <uapi/linux/sem.h>
struct task_struct; struct task_struct;
...@@ -30,7 +31,7 @@ struct sem { ...@@ -30,7 +31,7 @@ struct sem {
/* One sem_array data structure for each set of semaphores in the system. */ /* One sem_array data structure for each set of semaphores in the system. */
struct sem_array { struct sem_array {
struct kern_ipc_perm sem_perm; /* permissions .. see ipc.h */ struct kern_ipc_perm sem_perm; /* permissions .. see ipc.h */
time_t sem_ctime; /* create/last semctl() time */ time64_t sem_ctime; /* create/last semctl() time */
struct list_head pending_alter; /* pending operations */ struct list_head pending_alter; /* pending operations */
/* that alter the array */ /* that alter the array */
struct list_head pending_const; /* pending complex operations */ struct list_head pending_const; /* pending complex operations */
......
...@@ -12,9 +12,9 @@ struct shmid_kernel /* private to the kernel */ ...@@ -12,9 +12,9 @@ struct shmid_kernel /* private to the kernel */
struct file *shm_file; struct file *shm_file;
unsigned long shm_nattch; unsigned long shm_nattch;
unsigned long shm_segsz; unsigned long shm_segsz;
time_t shm_atim; time64_t shm_atim;
time_t shm_dtim; time64_t shm_dtim;
time_t shm_ctim; time64_t shm_ctim;
pid_t shm_cprid; pid_t shm_cprid;
pid_t shm_lprid; pid_t shm_lprid;
struct user_struct *mlock_user; struct user_struct *mlock_user;
......
...@@ -178,7 +178,7 @@ extern int do_setitimer(int which, struct itimerval *value, ...@@ -178,7 +178,7 @@ extern int do_setitimer(int which, struct itimerval *value,
struct itimerval *ovalue); struct itimerval *ovalue);
extern int do_getitimer(int which, struct itimerval *value); extern int do_getitimer(int which, struct itimerval *value);
extern long do_utimes(int dfd, const char __user *filename, struct timespec *times, int flags); extern long do_utimes(int dfd, const char __user *filename, struct timespec64 *times, int flags);
/* /*
* Similar to the struct tm in userspace <time.h>, but it needs to be here so * Similar to the struct tm in userspace <time.h>, but it needs to be here so
......
...@@ -110,7 +110,7 @@ static void __init free_hash(void) ...@@ -110,7 +110,7 @@ static void __init free_hash(void)
static long __init do_utime(char *filename, time_t mtime) static long __init do_utime(char *filename, time_t mtime)
{ {
struct timespec t[2]; struct timespec64 t[2];
t[0].tv_sec = mtime; t[0].tv_sec = mtime;
t[0].tv_nsec = 0; t[0].tv_nsec = 0;
......
...@@ -34,724 +34,48 @@ ...@@ -34,724 +34,48 @@
#include "util.h" #include "util.h"
struct compat_msgbuf { int get_compat_ipc64_perm(struct ipc64_perm *to,
compat_long_t mtype; struct compat_ipc64_perm __user *from)
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 err; struct compat_ipc64_perm v;
if (copy_from_user(&v, from, sizeof(v)))
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)))
return -EFAULT; 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, int get_compat_ipc_perm(struct ipc64_perm *to,
struct compat_semid64_ds __user *up64) struct compat_ipc_perm __user *from)
{ {
int err; struct compat_ipc_perm v;
if (copy_from_user(&v, from, sizeof(v)))
if (!access_ok(VERIFY_WRITE, up64, sizeof(*up64)))
return -EFAULT; return -EFAULT;
err = __put_compat_ipc64_perm(&sem64->sem_perm, &up64->sem_perm); to->uid = v.uid;
err |= __put_user(sem64->sem_otime, &up64->sem_otime); to->gid = v.gid;
err |= __put_user(sem64->sem_ctime, &up64->sem_ctime); to->mode = v.mode;
err |= __put_user(sem64->sem_nsems, &up64->sem_nsems); return 0;
return err;
} }
static inline int put_compat_semid_ds(struct semid64_ds *s, void to_compat_ipc64_perm(struct compat_ipc64_perm *to, struct ipc64_perm *from)
struct compat_semid_ds __user *up)
{ {
int err; to->key = from->key;
to->uid = from->uid;
if (!access_ok(VERIFY_WRITE, up, sizeof(*up))) to->gid = from->gid;
return -EFAULT; to->cuid = from->cuid;
err = __put_compat_ipc_perm(&s->sem_perm, &up->sem_perm); to->cgid = from->cgid;
err |= __put_user(s->sem_otime, &up->sem_otime); to->mode = from->mode;
err |= __put_user(s->sem_ctime, &up->sem_ctime); to->seq = from->seq;
err |= __put_user(s->sem_nsems, &up->sem_nsems);
return err;
} }
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; to->key = from->key;
int err, err2; SET_UID(to->uid, from->uid);
struct semid64_ds sem64; SET_GID(to->gid, from->gid);
struct semid64_ds __user *up64; SET_UID(to->cuid, from->cuid);
int version = compat_ipc_parse_version(&third); SET_GID(to->cgid, from->cgid);
to->mode = from->mode;
memset(&sem64, 0, sizeof(sem64)); to->seq = from->seq;
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);
} }
...@@ -668,11 +668,11 @@ static void __do_notify(struct mqueue_inode_info *info) ...@@ -668,11 +668,11 @@ static void __do_notify(struct mqueue_inode_info *info)
} }
static int prepare_timeout(const struct timespec __user *u_abs_timeout, 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; return -EFAULT;
if (!timespec_valid(ts)) if (!timespec64_valid(ts))
return -EINVAL; return -EINVAL;
return 0; return 0;
} }
...@@ -962,7 +962,7 @@ static inline void pipelined_receive(struct wake_q_head *wake_q, ...@@ -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, static int do_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr,
size_t msg_len, unsigned int msg_prio, size_t msg_len, unsigned int msg_prio,
struct timespec *ts) struct timespec64 *ts)
{ {
struct fd f; struct fd f;
struct inode *inode; struct inode *inode;
...@@ -979,7 +979,7 @@ static int do_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr, ...@@ -979,7 +979,7 @@ static int do_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr,
return -EINVAL; return -EINVAL;
if (ts) { if (ts) {
expires = timespec_to_ktime(*ts); expires = timespec64_to_ktime(*ts);
timeout = &expires; timeout = &expires;
} }
...@@ -1080,7 +1080,7 @@ static int do_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr, ...@@ -1080,7 +1080,7 @@ static int do_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr,
static int do_mq_timedreceive(mqd_t mqdes, char __user *u_msg_ptr, static int do_mq_timedreceive(mqd_t mqdes, char __user *u_msg_ptr,
size_t msg_len, unsigned int __user *u_msg_prio, size_t msg_len, unsigned int __user *u_msg_prio,
struct timespec *ts) struct timespec64 *ts)
{ {
ssize_t ret; ssize_t ret;
struct msg_msg *msg_ptr; struct msg_msg *msg_ptr;
...@@ -1092,7 +1092,7 @@ static int do_mq_timedreceive(mqd_t mqdes, char __user *u_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; struct posix_msg_tree_node *new_leaf = NULL;
if (ts) { if (ts) {
expires = timespec_to_ktime(*ts); expires = timespec64_to_ktime(*ts);
timeout = &expires; timeout = &expires;
} }
...@@ -1184,7 +1184,7 @@ SYSCALL_DEFINE5(mq_timedsend, mqd_t, mqdes, const char __user *, u_msg_ptr, ...@@ -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, size_t, msg_len, unsigned int, msg_prio,
const struct timespec __user *, u_abs_timeout) const struct timespec __user *, u_abs_timeout)
{ {
struct timespec ts, *p = NULL; struct timespec64 ts, *p = NULL;
if (u_abs_timeout) { if (u_abs_timeout) {
int res = prepare_timeout(u_abs_timeout, &ts); int res = prepare_timeout(u_abs_timeout, &ts);
if (res) if (res)
...@@ -1198,7 +1198,7 @@ SYSCALL_DEFINE5(mq_timedreceive, mqd_t, mqdes, char __user *, u_msg_ptr, ...@@ -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, size_t, msg_len, unsigned int __user *, u_msg_prio,
const struct timespec __user *, u_abs_timeout) const struct timespec __user *, u_abs_timeout)
{ {
struct timespec ts, *p = NULL; struct timespec64 ts, *p = NULL;
if (u_abs_timeout) { if (u_abs_timeout) {
int res = prepare_timeout(u_abs_timeout, &ts); int res = prepare_timeout(u_abs_timeout, &ts);
if (res) if (res)
...@@ -1475,11 +1475,11 @@ COMPAT_SYSCALL_DEFINE4(mq_open, const char __user *, u_name, ...@@ -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, 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; return -EFAULT;
if (!timespec_valid(ts)) if (!timespec64_valid(ts))
return -EINVAL; return -EINVAL;
return 0; return 0;
} }
...@@ -1489,7 +1489,7 @@ COMPAT_SYSCALL_DEFINE5(mq_timedsend, mqd_t, mqdes, ...@@ -1489,7 +1489,7 @@ COMPAT_SYSCALL_DEFINE5(mq_timedsend, mqd_t, mqdes,
compat_size_t, msg_len, unsigned int, msg_prio, compat_size_t, msg_len, unsigned int, msg_prio,
const struct compat_timespec __user *, u_abs_timeout) const struct compat_timespec __user *, u_abs_timeout)
{ {
struct timespec ts, *p = NULL; struct timespec64 ts, *p = NULL;
if (u_abs_timeout) { if (u_abs_timeout) {
int res = compat_prepare_timeout(u_abs_timeout, &ts); int res = compat_prepare_timeout(u_abs_timeout, &ts);
if (res) if (res)
...@@ -1504,7 +1504,7 @@ COMPAT_SYSCALL_DEFINE5(mq_timedreceive, mqd_t, mqdes, ...@@ -1504,7 +1504,7 @@ COMPAT_SYSCALL_DEFINE5(mq_timedreceive, mqd_t, mqdes,
compat_size_t, msg_len, unsigned int __user *, u_msg_prio, compat_size_t, msg_len, unsigned int __user *, u_msg_prio,
const struct compat_timespec __user *, u_abs_timeout) const struct compat_timespec __user *, u_abs_timeout)
{ {
struct timespec ts, *p = NULL; struct timespec64 ts, *p = NULL;
if (u_abs_timeout) { if (u_abs_timeout) {
int res = compat_prepare_timeout(u_abs_timeout, &ts); int res = compat_prepare_timeout(u_abs_timeout, &ts);
if (res) if (res)
......
...@@ -133,7 +133,7 @@ static int newque(struct ipc_namespace *ns, struct ipc_params *params) ...@@ -133,7 +133,7 @@ static int newque(struct ipc_namespace *ns, struct ipc_params *params)
} }
msq->q_stime = msq->q_rtime = 0; 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_cbytes = msq->q_qnum = 0;
msq->q_qbytes = ns->msg_ctlmnb; msq->q_qbytes = ns->msg_ctlmnb;
msq->q_lspid = msq->q_lrpid = 0; msq->q_lspid = msq->q_lrpid = 0;
...@@ -361,23 +361,17 @@ copy_msqid_from_user(struct msqid64_ds *out, void __user *buf, int version) ...@@ -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. * 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, 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 kern_ipc_perm *ipcp;
struct msqid64_ds uninitialized_var(msqid64);
struct msg_queue *msq; struct msg_queue *msq;
int err; int err;
if (cmd == IPC_SET) {
if (copy_msqid_from_user(&msqid64, buf, version))
return -EFAULT;
}
down_write(&msg_ids(ns).rwsem); down_write(&msg_ids(ns).rwsem);
rcu_read_lock(); rcu_read_lock();
ipcp = ipcctl_pre_down_nolock(ns, &msg_ids(ns), msqid, cmd, 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)) { if (IS_ERR(ipcp)) {
err = PTR_ERR(ipcp); err = PTR_ERR(ipcp);
goto out_unlock1; goto out_unlock1;
...@@ -399,20 +393,20 @@ static int msgctl_down(struct ipc_namespace *ns, int msqid, int cmd, ...@@ -399,20 +393,20 @@ static int msgctl_down(struct ipc_namespace *ns, int msqid, int cmd,
{ {
DEFINE_WAKE_Q(wake_q); DEFINE_WAKE_Q(wake_q);
if (msqid64.msg_qbytes > ns->msg_ctlmnb && if (msqid64->msg_qbytes > ns->msg_ctlmnb &&
!capable(CAP_SYS_RESOURCE)) { !capable(CAP_SYS_RESOURCE)) {
err = -EPERM; err = -EPERM;
goto out_unlock1; goto out_unlock1;
} }
ipc_lock_object(&msq->q_perm); ipc_lock_object(&msq->q_perm);
err = ipc_update_perm(&msqid64.msg_perm, ipcp); err = ipc_update_perm(&msqid64->msg_perm, ipcp);
if (err) if (err)
goto out_unlock0; 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 * Sleeping receivers might be excluded by
* stricter permissions. * stricter permissions.
...@@ -442,111 +436,89 @@ static int msgctl_down(struct ipc_namespace *ns, int msqid, int cmd, ...@@ -442,111 +436,89 @@ static int msgctl_down(struct ipc_namespace *ns, int msqid, int cmd,
return err; return err;
} }
static int msgctl_nolock(struct ipc_namespace *ns, int msqid, static int msgctl_info(struct ipc_namespace *ns, int msqid,
int cmd, int version, void __user *buf) int cmd, struct msginfo *msginfo)
{ {
int err; int err;
struct msg_queue *msq; int max_id;
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;
memset(&msginfo, 0, sizeof(msginfo)); /*
msginfo.msgmni = ns->msg_ctlmni; * We must not return kernel stack data.
msginfo.msgmax = ns->msg_ctlmax; * due to padding, it's not enough
msginfo.msgmnb = ns->msg_ctlmnb; * to set all member fields.
msginfo.msgssz = MSGSSZ; */
msginfo.msgseg = MSGSEG; err = security_msg_queue_msgctl(NULL, cmd);
down_read(&msg_ids(ns).rwsem); if (err)
if (cmd == MSG_INFO) { return err;
msginfo.msgpool = msg_ids(ns).in_use;
msginfo.msgmap = atomic_read(&ns->msg_hdrs); memset(msginfo, 0, sizeof(*msginfo));
msginfo.msgtql = atomic_read(&ns->msg_bytes); msginfo->msgmni = ns->msg_ctlmni;
} else { msginfo->msgmax = ns->msg_ctlmax;
msginfo.msgmap = MSGMAP; msginfo->msgmnb = ns->msg_ctlmnb;
msginfo.msgpool = MSGPOOL; msginfo->msgssz = MSGSSZ;
msginfo.msgtql = MSGTQL; msginfo->msgseg = MSGSEG;
} down_read(&msg_ids(ns).rwsem);
max_id = ipc_get_maxid(&msg_ids(ns)); if (cmd == MSG_INFO) {
up_read(&msg_ids(ns).rwsem); msginfo->msgpool = msg_ids(ns).in_use;
if (copy_to_user(buf, &msginfo, sizeof(struct msginfo))) msginfo->msgmap = atomic_read(&ns->msg_hdrs);
return -EFAULT; msginfo->msgtql = atomic_read(&ns->msg_bytes);
return (max_id < 0) ? 0 : max_id; } 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: static int msgctl_stat(struct ipc_namespace *ns, int msqid,
case IPC_STAT: int cmd, struct msqid64_ds *p)
{ {
struct msqid64_ds tbuf; int err;
int success_return; struct msg_queue *msq;
int success_return;
if (!buf)
return -EFAULT;
memset(&tbuf, 0, sizeof(tbuf));
rcu_read_lock(); memset(p, 0, sizeof(*p));
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;
}
err = -EACCES; rcu_read_lock();
if (ipcperms(ns, &msq->q_perm, S_IRUGO)) if (cmd == MSG_STAT) {
msq = msq_obtain_object(ns, msqid);
if (IS_ERR(msq)) {
err = PTR_ERR(msq);
goto out_unlock; goto out_unlock;
}
err = security_msg_queue_msgctl(msq, cmd); success_return = msq->q_perm.id;
if (err) } else {
msq = msq_obtain_object_check(ns, msqid);
if (IS_ERR(msq)) {
err = PTR_ERR(msq);
goto out_unlock; goto out_unlock;
}
success_return = 0;
}
kernel_to_ipc64_perm(&msq->q_perm, &tbuf.msg_perm); err = -EACCES;
tbuf.msg_stime = msq->q_stime; if (ipcperms(ns, &msq->q_perm, S_IRUGO))
tbuf.msg_rtime = msq->q_rtime; goto out_unlock;
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();
if (copy_msqid_to_user(buf, &tbuf, version)) err = security_msg_queue_msgctl(msq, cmd);
return -EFAULT; if (err)
return success_return; 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 success_return;
return -EINVAL;
}
return err;
out_unlock: out_unlock:
rcu_read_unlock(); rcu_read_unlock();
return err; return err;
...@@ -556,6 +528,8 @@ SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf) ...@@ -556,6 +528,8 @@ SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf)
{ {
int version; int version;
struct ipc_namespace *ns; struct ipc_namespace *ns;
struct msqid64_ds msqid64;
int err;
if (msqid < 0 || cmd < 0) if (msqid < 0 || cmd < 0)
return -EINVAL; return -EINVAL;
...@@ -565,18 +539,147 @@ SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf) ...@@ -565,18 +539,147 @@ SYSCALL_DEFINE3(msgctl, int, msqid, int, cmd, struct msqid_ds __user *, buf)
switch (cmd) { switch (cmd) {
case IPC_INFO: 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 MSG_STAT: /* msqid is an index rather than a msg queue id */
case IPC_STAT: 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: case IPC_SET:
if (copy_msqid_from_user(&msqid64, buf, version))
return -EFAULT;
/* fallthru */
case IPC_RMID: case IPC_RMID:
return msgctl_down(ns, msqid, cmd, buf, version); return msgctl_down(ns, msqid, cmd, &msqid64);
default: default:
return -EINVAL; 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) static int testmsg(struct msg_msg *msg, long type, int mode)
{ {
switch (mode) { switch (mode) {
...@@ -627,7 +730,7 @@ static inline int pipelined_send(struct msg_queue *msq, struct msg_msg *msg, ...@@ -627,7 +730,7 @@ static inline int pipelined_send(struct msg_queue *msq, struct msg_msg *msg,
return 0; 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) size_t msgsz, int msgflg)
{ {
struct msg_queue *msq; struct msg_queue *msq;
...@@ -750,6 +853,25 @@ SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz, ...@@ -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); 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) static inline int convert_mode(long *msgtyp, int msgflg)
{ {
if (msgflg & MSG_COPY) if (msgflg & MSG_COPY)
...@@ -846,7 +968,7 @@ static struct msg_msg *find_msg(struct msg_queue *msq, long *msgtyp, int mode) ...@@ -846,7 +968,7 @@ static struct msg_msg *find_msg(struct msg_queue *msq, long *msgtyp, int mode)
return found ?: ERR_PTR(-EAGAIN); 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)) long (*msg_handler)(void __user *, struct msg_msg *, size_t))
{ {
int mode; int mode;
...@@ -1010,6 +1132,28 @@ SYSCALL_DEFINE5(msgrcv, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz, ...@@ -1010,6 +1132,28 @@ SYSCALL_DEFINE5(msgrcv, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz,
return do_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill); 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
int msg_init_ns(struct ipc_namespace *ns) int msg_init_ns(struct ipc_namespace *ns)
{ {
...@@ -1039,7 +1183,7 @@ static int sysvipc_msg_proc_show(struct seq_file *s, void *it) ...@@ -1039,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); struct msg_queue *msq = container_of(ipcp, struct msg_queue, q_perm);
seq_printf(s, 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.key,
msq->q_perm.id, msq->q_perm.id,
msq->q_perm.mode, msq->q_perm.mode,
......
...@@ -512,7 +512,7 @@ static int newary(struct ipc_namespace *ns, struct ipc_params *params) ...@@ -512,7 +512,7 @@ static int newary(struct ipc_namespace *ns, struct ipc_params *params)
INIT_LIST_HEAD(&sma->pending_const); INIT_LIST_HEAD(&sma->pending_const);
INIT_LIST_HEAD(&sma->list_id); INIT_LIST_HEAD(&sma->list_id);
sma->sem_nsems = nsems; 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); retval = ipc_addid(&sem_ids(ns), &sma->sem_perm, ns->sc_semmni);
if (retval < 0) { if (retval < 0) {
...@@ -1163,14 +1163,14 @@ static unsigned long copy_semid_to_user(void __user *buf, struct semid64_ds *in, ...@@ -1163,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; int i;
time_t res; time64_t res;
res = sma->sems[0].sem_otime; res = sma->sems[0].sem_otime;
for (i = 1; i < sma->sem_nsems; i++) { 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) if (to > res)
res = to; res = to;
...@@ -1178,112 +1178,95 @@ static time_t get_semotime(struct sem_array *sma) ...@@ -1178,112 +1178,95 @@ static time_t get_semotime(struct sem_array *sma)
return res; return res;
} }
static int semctl_nolock(struct ipc_namespace *ns, int semid, static int semctl_stat(struct ipc_namespace *ns, int semid,
int cmd, int version, void __user *p) int cmd, struct semid64_ds *semid64)
{ {
int err;
struct sem_array *sma; struct sem_array *sma;
int id = 0;
int err;
switch (cmd) { memset(semid64, 0, sizeof(*semid64));
case IPC_INFO:
case SEM_INFO:
{
struct seminfo seminfo;
int max_id;
err = security_sem_semctl(NULL, cmd);
if (err)
return err;
memset(&seminfo, 0, sizeof(seminfo)); rcu_read_lock();
seminfo.semmni = ns->sc_semmni; if (cmd == SEM_STAT) {
seminfo.semmns = ns->sc_semmns; sma = sem_obtain_object(ns, semid);
seminfo.semmsl = ns->sc_semmsl; if (IS_ERR(sma)) {
seminfo.semopm = ns->sc_semopm; err = PTR_ERR(sma);
seminfo.semvmx = SEMVMX; goto out_unlock;
seminfo.semmnu = SEMMNU; }
seminfo.semmap = SEMMAP; id = sma->sem_perm.id;
seminfo.semume = SEMUME; } else {
down_read(&sem_ids(ns).rwsem); sma = sem_obtain_object_check(ns, semid);
if (cmd == SEM_INFO) { if (IS_ERR(sma)) {
seminfo.semusz = sem_ids(ns).in_use; err = PTR_ERR(sma);
seminfo.semaem = ns->used_sems; goto out_unlock;
} 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;
} }
case IPC_STAT:
case SEM_STAT:
{
struct semid64_ds tbuf;
int id = 0;
memset(&tbuf, 0, sizeof(tbuf));
rcu_read_lock(); err = -EACCES;
if (cmd == SEM_STAT) { if (ipcperms(ns, &sma->sem_perm, S_IRUGO))
sma = sem_obtain_object(ns, semid); goto out_unlock;
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; err = security_sem_semctl(sma, cmd);
if (ipcperms(ns, &sma->sem_perm, S_IRUGO)) if (err)
goto out_unlock; goto out_unlock;
err = security_sem_semctl(sma, cmd); kernel_to_ipc64_perm(&sma->sem_perm, &semid64->sem_perm);
if (err) semid64->sem_otime = get_semotime(sma);
goto out_unlock; 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: out_unlock:
rcu_read_unlock(); rcu_read_unlock();
return err; 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, static int semctl_setval(struct ipc_namespace *ns, int semid, int semnum,
unsigned long arg) int val)
{ {
struct sem_undo *un; struct sem_undo *un;
struct sem_array *sma; struct sem_array *sma;
struct sem *curr; struct sem *curr;
int err, val; int err;
DEFINE_WAKE_Q(wake_q); 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) if (val > SEMVMX || val < 0)
return -ERANGE; return -ERANGE;
...@@ -1327,7 +1310,7 @@ static int semctl_setval(struct ipc_namespace *ns, int semid, int semnum, ...@@ -1327,7 +1310,7 @@ static int semctl_setval(struct ipc_namespace *ns, int semid, int semnum,
curr->semval = val; curr->semval = val;
curr->sempid = task_tgid_vnr(current); 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 */ /* maybe some queued-up processes were waiting for this */
do_smart_update(sma, NULL, 0, 0, &wake_q); do_smart_update(sma, NULL, 0, 0, &wake_q);
sem_unlock(sma, -1); sem_unlock(sma, -1);
...@@ -1455,7 +1438,7 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum, ...@@ -1455,7 +1438,7 @@ static int semctl_main(struct ipc_namespace *ns, int semid, int semnum,
for (i = 0; i < nsems; i++) for (i = 0; i < nsems; i++)
un->semadj[i] = 0; 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 */ /* maybe some queued-up processes were waiting for this */
do_smart_update(sma, NULL, 0, 0, &wake_q); do_smart_update(sma, NULL, 0, 0, &wake_q);
err = 0; err = 0;
...@@ -1532,23 +1515,17 @@ copy_semid_from_user(struct semid64_ds *out, void __user *buf, int version) ...@@ -1532,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. * NOTE: no locks must be held, the rwsem is taken inside this function.
*/ */
static int semctl_down(struct ipc_namespace *ns, int semid, 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; struct sem_array *sma;
int err; int err;
struct semid64_ds semid64;
struct kern_ipc_perm *ipcp; 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); down_write(&sem_ids(ns).rwsem);
rcu_read_lock(); rcu_read_lock();
ipcp = ipcctl_pre_down_nolock(ns, &sem_ids(ns), semid, cmd, ipcp = ipcctl_pre_down_nolock(ns, &sem_ids(ns), semid, cmd,
&semid64.sem_perm, 0); &semid64->sem_perm, 0);
if (IS_ERR(ipcp)) { if (IS_ERR(ipcp)) {
err = PTR_ERR(ipcp); err = PTR_ERR(ipcp);
goto out_unlock1; goto out_unlock1;
...@@ -1568,10 +1545,10 @@ static int semctl_down(struct ipc_namespace *ns, int semid, ...@@ -1568,10 +1545,10 @@ static int semctl_down(struct ipc_namespace *ns, int semid,
goto out_up; goto out_up;
case IPC_SET: case IPC_SET:
sem_lock(sma, NULL, -1); sem_lock(sma, NULL, -1);
err = ipc_update_perm(&semid64.sem_perm, ipcp); err = ipc_update_perm(&semid64->sem_perm, ipcp);
if (err) if (err)
goto out_unlock0; goto out_unlock0;
sma->sem_ctime = get_seconds(); sma->sem_ctime = ktime_get_real_seconds();
break; break;
default: default:
err = -EINVAL; err = -EINVAL;
...@@ -1592,6 +1569,8 @@ SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, unsigned long, arg) ...@@ -1592,6 +1569,8 @@ SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, unsigned long, arg)
int version; int version;
struct ipc_namespace *ns; struct ipc_namespace *ns;
void __user *p = (void __user *)arg; void __user *p = (void __user *)arg;
struct semid64_ds semid64;
int err;
if (semid < 0) if (semid < 0)
return -EINVAL; return -EINVAL;
...@@ -1602,25 +1581,136 @@ SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, unsigned long, arg) ...@@ -1602,25 +1581,136 @@ SYSCALL_DEFINE4(semctl, int, semid, int, semnum, int, cmd, unsigned long, arg)
switch (cmd) { switch (cmd) {
case IPC_INFO: case IPC_INFO:
case SEM_INFO: case SEM_INFO:
return semctl_info(ns, semid, cmd, p);
case IPC_STAT: case IPC_STAT:
case SEM_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 GETALL:
case GETVAL: case GETVAL:
case GETPID: case GETPID:
case GETNCNT: case GETNCNT:
case GETZCNT: 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: case SETALL:
return semctl_main(ns, semid, semnum, cmd, p); return semctl_main(ns, semid, semnum, cmd, p);
case SETVAL: case SETVAL:
return semctl_setval(ns, semid, semnum, arg); return semctl_setval(ns, semid, semnum, arg);
case IPC_RMID:
case IPC_SET: 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: default:
return -EINVAL; return -EINVAL;
} }
} }
#endif
/* If the task doesn't already have a undo_list, then allocate one /* 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, * here. We guarantee there is only one thread using this undo list,
...@@ -1766,8 +1856,8 @@ static struct sem_undo *find_alloc_undo(struct ipc_namespace *ns, int semid) ...@@ -1766,8 +1856,8 @@ static struct sem_undo *find_alloc_undo(struct ipc_namespace *ns, int semid)
return un; return un;
} }
SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops, static long do_semtimedop(int semid, struct sembuf __user *tsops,
unsigned, nsops, const struct timespec __user *, timeout) unsigned nsops, const struct timespec64 *timeout)
{ {
int error = -EINVAL; int error = -EINVAL;
struct sem_array *sma; struct sem_array *sma;
...@@ -1798,17 +1888,12 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops, ...@@ -1798,17 +1888,12 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops,
} }
if (timeout) { if (timeout) {
struct timespec _timeout; if (timeout->tv_sec < 0 || timeout->tv_nsec < 0 ||
if (copy_from_user(&_timeout, timeout, sizeof(*timeout))) { timeout->tv_nsec >= 1000000000L) {
error = -EFAULT;
goto out_free;
}
if (_timeout.tv_sec < 0 || _timeout.tv_nsec < 0 ||
_timeout.tv_nsec >= 1000000000L) {
error = -EINVAL; error = -EINVAL;
goto out_free; goto out_free;
} }
jiffies_left = timespec_to_jiffies(&_timeout); jiffies_left = timespec64_to_jiffies(timeout);
} }
max = 0; max = 0;
...@@ -2023,10 +2108,37 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops, ...@@ -2023,10 +2108,37 @@ SYSCALL_DEFINE4(semtimedop, int, semid, struct sembuf __user *, tsops,
return error; 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, SYSCALL_DEFINE3(semop, int, semid, struct sembuf __user *, tsops,
unsigned, nsops) 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 /* If CLONE_SYSVSEM is set, establish sharing of SEM_UNDO state between
...@@ -2183,7 +2295,7 @@ static int sysvipc_sem_proc_show(struct seq_file *s, void *it) ...@@ -2183,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 user_namespace *user_ns = seq_user_ns(s);
struct kern_ipc_perm *ipcp = it; struct kern_ipc_perm *ipcp = it;
struct sem_array *sma = container_of(ipcp, struct sem_array, sem_perm); 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 * The proc interface isn't aware of sem_lock(), it calls
...@@ -2196,7 +2308,7 @@ static int sysvipc_sem_proc_show(struct seq_file *s, void *it) ...@@ -2196,7 +2308,7 @@ static int sysvipc_sem_proc_show(struct seq_file *s, void *it)
sem_otime = get_semotime(sma); sem_otime = get_semotime(sma);
seq_printf(s, 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.key,
sma->sem_perm.id, sma->sem_perm.id,
sma->sem_perm.mode, sma->sem_perm.mode,
......
...@@ -202,7 +202,7 @@ static int __shm_open(struct vm_area_struct *vma) ...@@ -202,7 +202,7 @@ static int __shm_open(struct vm_area_struct *vma)
if (IS_ERR(shp)) if (IS_ERR(shp))
return PTR_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_lprid = task_tgid_vnr(current);
shp->shm_nattch++; shp->shm_nattch++;
shm_unlock(shp); shm_unlock(shp);
...@@ -289,7 +289,7 @@ static void shm_close(struct vm_area_struct *vma) ...@@ -289,7 +289,7 @@ static void shm_close(struct vm_area_struct *vma)
goto done; /* no-op */ goto done; /* no-op */
shp->shm_lprid = task_tgid_vnr(current); shp->shm_lprid = task_tgid_vnr(current);
shp->shm_dtim = get_seconds(); shp->shm_dtim = ktime_get_real_seconds();
shp->shm_nattch--; shp->shm_nattch--;
if (shm_may_destroy(ns, shp)) if (shm_may_destroy(ns, shp))
shm_destroy(ns, shp); shm_destroy(ns, shp);
...@@ -594,7 +594,7 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params) ...@@ -594,7 +594,7 @@ static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
shp->shm_cprid = task_tgid_vnr(current); shp->shm_cprid = task_tgid_vnr(current);
shp->shm_lprid = 0; shp->shm_lprid = 0;
shp->shm_atim = shp->shm_dtim = 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_segsz = size;
shp->shm_nattch = 0; shp->shm_nattch = 0;
shp->shm_file = file; shp->shm_file = file;
...@@ -815,23 +815,17 @@ static void shm_get_stat(struct ipc_namespace *ns, unsigned long *rss, ...@@ -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. * 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, 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 kern_ipc_perm *ipcp;
struct shmid64_ds shmid64;
struct shmid_kernel *shp; struct shmid_kernel *shp;
int err; int err;
if (cmd == IPC_SET) {
if (copy_shmid_from_user(&shmid64, buf, version))
return -EFAULT;
}
down_write(&shm_ids(ns).rwsem); down_write(&shm_ids(ns).rwsem);
rcu_read_lock(); rcu_read_lock();
ipcp = ipcctl_pre_down_nolock(ns, &shm_ids(ns), shmid, cmd, ipcp = ipcctl_pre_down_nolock(ns, &shm_ids(ns), shmid, cmd,
&shmid64.shm_perm, 0); &shmid64->shm_perm, 0);
if (IS_ERR(ipcp)) { if (IS_ERR(ipcp)) {
err = PTR_ERR(ipcp); err = PTR_ERR(ipcp);
goto out_unlock1; goto out_unlock1;
...@@ -851,10 +845,10 @@ static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd, ...@@ -851,10 +845,10 @@ static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd,
goto out_up; goto out_up;
case IPC_SET: case IPC_SET:
ipc_lock_object(&shp->shm_perm); ipc_lock_object(&shp->shm_perm);
err = ipc_update_perm(&shmid64.shm_perm, ipcp); err = ipc_update_perm(&shmid64->shm_perm, ipcp);
if (err) if (err)
goto out_unlock0; goto out_unlock0;
shp->shm_ctim = get_seconds(); shp->shm_ctim = ktime_get_real_seconds();
break; break;
default: default:
err = -EINVAL; err = -EINVAL;
...@@ -870,125 +864,175 @@ static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd, ...@@ -870,125 +864,175 @@ static int shmctl_down(struct ipc_namespace *ns, int shmid, int cmd,
return err; return err;
} }
static int shmctl_nolock(struct ipc_namespace *ns, int shmid, static int shmctl_ipc_info(struct ipc_namespace *ns,
int cmd, int version, void __user *buf) struct shminfo64 *shminfo)
{ {
int err; int err = security_shm_shmctl(NULL, IPC_INFO);
struct shmid_kernel *shp; if (!err) {
memset(shminfo, 0, sizeof(*shminfo));
/* preliminary security checks for *_INFO */ shminfo->shmmni = shminfo->shmseg = ns->shm_ctlmni;
if (cmd == IPC_INFO || cmd == SHM_INFO) { shminfo->shmmax = ns->shm_ctlmax;
err = security_shm_shmctl(NULL, cmd); shminfo->shmall = ns->shm_ctlall;
if (err) shminfo->shmmin = SHMMIN;
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;
down_read(&shm_ids(ns).rwsem); down_read(&shm_ids(ns).rwsem);
err = ipc_get_maxid(&shm_ids(ns)); err = ipc_get_maxid(&shm_ids(ns));
up_read(&shm_ids(ns).rwsem); up_read(&shm_ids(ns).rwsem);
if (err < 0) if (err < 0)
err = 0; err = 0;
goto out;
} }
case SHM_INFO: return err;
{ }
struct shm_info shm_info;
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); down_read(&shm_ids(ns).rwsem);
shm_info.used_ids = shm_ids(ns).in_use; shm_info->used_ids = shm_ids(ns).in_use;
shm_get_stat(ns, &shm_info.shm_rss, &shm_info.shm_swp); shm_get_stat(ns, &shm_info->shm_rss, &shm_info->shm_swp);
shm_info.shm_tot = ns->shm_tot; shm_info->shm_tot = ns->shm_tot;
shm_info.swap_attempts = 0; shm_info->swap_attempts = 0;
shm_info.swap_successes = 0; shm_info->swap_successes = 0;
err = ipc_get_maxid(&shm_ids(ns)); err = ipc_get_maxid(&shm_ids(ns));
up_read(&shm_ids(ns).rwsem); up_read(&shm_ids(ns).rwsem);
if (copy_to_user(buf, &shm_info, sizeof(shm_info))) { if (err < 0)
err = -EFAULT; err = 0;
goto out; }
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; err = -EACCES;
goto out; 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; audit_ipc_obj(&(shp->shm_perm));
if (ipcperms(ns, &shp->shm_perm, S_IRUGO)) err = security_shm_shmctl(shp, cmd);
goto out_unlock; if (err)
goto out_unlock1;
err = security_shm_shmctl(shp, cmd); ipc_lock_object(&shp->shm_perm);
if (err)
goto out_unlock;
memset(&tbuf, 0, sizeof(tbuf)); /* check if shm_destroy() is tearing down shp */
kernel_to_ipc64_perm(&shp->shm_perm, &tbuf.shm_perm); if (!ipc_valid_object(&shp->shm_perm)) {
tbuf.shm_segsz = shp->shm_segsz; err = -EIDRM;
tbuf.shm_atime = shp->shm_atim; goto out_unlock0;
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;
} }
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(); rcu_read_unlock();
out:
return err; return err;
} }
SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf) SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
{ {
struct shmid_kernel *shp;
int err, version; int err, version;
struct ipc_namespace *ns; struct ipc_namespace *ns;
struct shmid64_ds sem64;
if (cmd < 0 || shmid < 0) if (cmd < 0 || shmid < 0)
return -EINVAL; return -EINVAL;
...@@ -997,92 +1041,222 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf) ...@@ -997,92 +1041,222 @@ SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, struct shmid_ds __user *, buf)
ns = current->nsproxy->ipc_ns; ns = current->nsproxy->ipc_ns;
switch (cmd) { switch (cmd) {
case IPC_INFO: case IPC_INFO: {
case SHM_INFO: struct shminfo64 shminfo;
err = shmctl_ipc_info(ns, &shminfo);
if (err < 0)
return err;
if (copy_shminfo_to_user(buf, &shminfo, version))
err = -EFAULT;
return err;
}
case SHM_INFO: {
struct shm_info shm_info;
err = shmctl_shm_info(ns, &shm_info);
if (err < 0)
return err;
if (copy_to_user(buf, &shm_info, sizeof(shm_info)))
err = -EFAULT;
return err;
}
case SHM_STAT: case SHM_STAT:
case IPC_STAT: case IPC_STAT: {
return shmctl_nolock(ns, shmid, cmd, version, buf); err = shmctl_stat(ns, shmid, cmd, &sem64);
case IPC_RMID: if (err < 0)
return err;
if (copy_shmid_to_user(buf, &sem64, version))
err = -EFAULT;
return err;
}
case IPC_SET: 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_LOCK:
case SHM_UNLOCK: case SHM_UNLOCK:
{ return shmctl_do_lock(ns, shmid, cmd);
struct file *shm_file; default:
return -EINVAL;
}
}
rcu_read_lock(); #ifdef CONFIG_COMPAT
shp = shm_obtain_object_check(ns, shmid);
if (IS_ERR(shp)) { struct compat_shmid_ds {
err = PTR_ERR(shp); struct compat_ipc_perm shm_perm;
goto out_unlock1; 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)); struct compat_shminfo64 {
err = security_shm_shmctl(shp, cmd); compat_ulong_t shmmax;
if (err) compat_ulong_t shmmin;
goto out_unlock1; 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 */ static int copy_compat_shminfo_to_user(void __user *buf, struct shminfo64 *in,
if (!ipc_valid_object(&shp->shm_perm)) { int version)
err = -EIDRM; {
goto out_unlock0; 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)) { static int put_compat_shm_info(struct shm_info *ip,
kuid_t euid = current_euid(); struct compat_shm_info __user *uip)
{
if (!uid_eq(euid, shp->shm_perm.uid) && struct compat_shm_info info;
!uid_eq(euid, shp->shm_perm.cuid)) {
err = -EPERM; memset(&info, 0, sizeof(info));
goto out_unlock0; info.used_ids = ip->used_ids;
} info.shm_tot = ip->shm_tot;
if (cmd == SHM_LOCK && !rlimit(RLIMIT_MEMLOCK)) { info.shm_rss = ip->shm_rss;
err = -EPERM; info.shm_swp = ip->shm_swp;
goto out_unlock0; 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; static int copy_compat_shmid_to_user(void __user *buf, struct shmid64_ds *in,
if (is_file_hugepages(shm_file)) int version)
goto out_unlock0; {
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) { static int copy_compat_shmid_from_user(struct shmid64_ds *out, void __user *buf,
struct user_struct *user = current_user(); 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); COMPAT_SYSCALL_DEFINE3(shmctl, int, shmid, int, cmd, void __user *, uptr)
if (!err && !(shp->shm_perm.mode & SHM_LOCKED)) { {
shp->shm_perm.mode |= SHM_LOCKED; struct ipc_namespace *ns;
shp->mlock_user = user; struct shmid64_ds sem64;
} int version = compat_ipc_parse_version(&cmd);
goto out_unlock0; int err;
}
/* SHM_UNLOCK */ ns = current->nsproxy->ipc_ns;
if (!(shp->shm_perm.mode & SHM_LOCKED))
goto out_unlock0; if (cmd < 0 || shmid < 0)
shmem_lock(shm_file, 0, shp->mlock_user); return -EINVAL;
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); 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; 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: default:
return -EINVAL; return -EINVAL;
} }
out_unlock0:
ipc_unlock_object(&shp->shm_perm);
out_unlock1:
rcu_read_unlock();
return err; return err;
} }
#endif
/* /*
* Fix shmaddr, allocate descriptor, map shm, add attach descriptor to lists. * 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) ...@@ -1267,6 +1441,25 @@ SYSCALL_DEFINE3(shmat, int, shmid, char __user *, shmaddr, int, shmflg)
return (long)ret; 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. * detach and kill segment if marked destroyed.
* The work is done in shm_close. * The work is done in shm_close.
...@@ -1397,7 +1590,7 @@ static int sysvipc_shm_proc_show(struct seq_file *s, void *it) ...@@ -1397,7 +1590,7 @@ static int sysvipc_shm_proc_show(struct seq_file *s, void *it)
seq_printf(s, seq_printf(s,
"%10d %10d %4o " SIZE_SPEC " %5u %5u " "%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", SIZE_SPEC " " SIZE_SPEC "\n",
shp->shm_perm.key, shp->shm_perm.key,
shp->shm_perm.id, shp->shm_perm.id,
......
...@@ -5,12 +5,12 @@ ...@@ -5,12 +5,12 @@
* the individual syscalls instead. * the individual syscalls instead.
*/ */
#include <linux/unistd.h> #include <linux/unistd.h>
#include <linux/syscalls.h>
#ifdef __ARCH_WANT_SYS_IPC #ifdef __ARCH_WANT_SYS_IPC
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/ipc.h> #include <linux/ipc.h>
#include <linux/shm.h> #include <linux/shm.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
SYSCALL_DEFINE6(ipc, unsigned int, call, int, first, unsigned long, second, 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, ...@@ -97,3 +97,91 @@ SYSCALL_DEFINE6(ipc, unsigned int, call, int, first, unsigned long, second,
} }
} }
#endif #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
...@@ -194,4 +194,34 @@ int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids, ...@@ -194,4 +194,34 @@ int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids,
const struct ipc_ops *ops, struct ipc_params *params); const struct ipc_ops *ops, struct ipc_params *params);
void free_ipcs(struct ipc_namespace *ns, struct ipc_ids *ids, void free_ipcs(struct ipc_namespace *ns, struct ipc_ids *ids,
void (*free)(struct ipc_namespace *, struct kern_ipc_perm *)); 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 #endif
...@@ -182,7 +182,7 @@ struct audit_context { ...@@ -182,7 +182,7 @@ struct audit_context {
mqd_t mqdes; mqd_t mqdes;
size_t msg_len; size_t msg_len;
unsigned int msg_prio; unsigned int msg_prio;
struct timespec abs_timeout; struct timespec64 abs_timeout;
} mq_sendrecv; } mq_sendrecv;
struct { struct {
int oflag; int oflag;
......
...@@ -1235,11 +1235,11 @@ static void show_special(struct audit_context *context, int *call_panic) ...@@ -1235,11 +1235,11 @@ static void show_special(struct audit_context *context, int *call_panic)
case AUDIT_MQ_SENDRECV: case AUDIT_MQ_SENDRECV:
audit_log_format(ab, audit_log_format(ab,
"mqdes=%d msg_len=%zd msg_prio=%u " "mqdes=%d msg_len=%zd msg_prio=%u "
"abs_timeout_sec=%ld abs_timeout_nsec=%ld", "abs_timeout_sec=%lld abs_timeout_nsec=%ld",
context->mq_sendrecv.mqdes, context->mq_sendrecv.mqdes,
context->mq_sendrecv.msg_len, context->mq_sendrecv.msg_len,
context->mq_sendrecv.msg_prio, context->mq_sendrecv.msg_prio,
context->mq_sendrecv.abs_timeout.tv_sec, (long long) context->mq_sendrecv.abs_timeout.tv_sec,
context->mq_sendrecv.abs_timeout.tv_nsec); context->mq_sendrecv.abs_timeout.tv_nsec);
break; break;
case AUDIT_MQ_NOTIFY: case AUDIT_MQ_NOTIFY:
...@@ -2083,15 +2083,15 @@ void __audit_mq_open(int oflag, umode_t mode, struct mq_attr *attr) ...@@ -2083,15 +2083,15 @@ void __audit_mq_open(int oflag, umode_t mode, struct mq_attr *attr)
* *
*/ */
void __audit_mq_sendrecv(mqd_t mqdes, size_t msg_len, unsigned int msg_prio, void __audit_mq_sendrecv(mqd_t mqdes, size_t msg_len, unsigned int msg_prio,
const struct timespec *abs_timeout) const struct timespec64 *abs_timeout)
{ {
struct audit_context *context = current->audit_context; struct audit_context *context = current->audit_context;
struct timespec *p = &context->mq_sendrecv.abs_timeout; struct timespec64 *p = &context->mq_sendrecv.abs_timeout;
if (abs_timeout) if (abs_timeout)
memcpy(p, abs_timeout, sizeof(struct timespec)); memcpy(p, abs_timeout, sizeof(*p));
else else
memset(p, 0, sizeof(struct timespec)); memset(p, 0, sizeof(*p));
context->mq_sendrecv.mqdes = mqdes; context->mq_sendrecv.mqdes = mqdes;
context->mq_sendrecv.msg_len = msg_len; context->mq_sendrecv.msg_len = msg_len;
......
...@@ -200,29 +200,6 @@ int compat_put_timespec(const struct timespec *ts, void __user *uts) ...@@ -200,29 +200,6 @@ int compat_put_timespec(const struct timespec *ts, void __user *uts)
} }
EXPORT_SYMBOL_GPL(compat_put_timespec); EXPORT_SYMBOL_GPL(compat_put_timespec);
int compat_convert_timespec(struct timespec __user **kts,
const void __user *cts)
{
struct timespec ts;
struct timespec __user *uts;
if (!cts || COMPAT_USE_64BIT_TIME) {
*kts = (struct timespec __user *)cts;
return 0;
}
uts = compat_alloc_user_space(sizeof(ts));
if (!uts)
return -EFAULT;
if (compat_get_timespec(&ts, cts))
return -EFAULT;
if (copy_to_user(uts, &ts, sizeof(ts)))
return -EFAULT;
*kts = uts;
return 0;
}
int get_compat_itimerval(struct itimerval *o, const struct compat_itimerval __user *i) int get_compat_itimerval(struct itimerval *o, const struct compat_itimerval __user *i)
{ {
struct compat_itimerval v32; struct compat_itimerval v32;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment