Commit bdec0145 authored by Arnd Bergmann's avatar Arnd Bergmann Committed by Russell King (Oracle)

ARM: 9114/1: oabi-compat: rework sys_semtimedop emulation

sys_oabi_semtimedop() is one of the last users of set_fs() on Arm. To
remove this one, expose the internal code of the actual implementation
that operates on a kernel pointer and call it directly after copying.

There should be no measurable impact on the normal execution of this
function, and it makes the overly long function a little shorter, which
may help readability.

While reworking the oabi version, make it behave a little more like
the native one, using kvmalloc_array() and restructure the code
flow in a similar way.

The naming of __do_semtimedop() is not very good, I hope someone can
come up with a better name.

One regression was spotted by kernel test robot <rong.a.chen@intel.com>
and fixed before the first mailing list submission.
Acked-by: default avatarChristoph Hellwig <hch@lst.de>
Signed-off-by: default avatarArnd Bergmann <arnd@arndb.de>
Signed-off-by: default avatarRussell King (Oracle) <rmk+kernel@armlinux.org.uk>
parent 249dbe74
...@@ -80,6 +80,7 @@ ...@@ -80,6 +80,7 @@
#include <linux/socket.h> #include <linux/socket.h>
#include <linux/net.h> #include <linux/net.h>
#include <linux/ipc.h> #include <linux/ipc.h>
#include <linux/ipc_namespace.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/slab.h> #include <linux/slab.h>
...@@ -302,46 +303,52 @@ struct oabi_sembuf { ...@@ -302,46 +303,52 @@ struct oabi_sembuf {
unsigned short __pad; unsigned short __pad;
}; };
#define sc_semopm sem_ctls[2]
#ifdef CONFIG_SYSVIPC
asmlinkage long sys_oabi_semtimedop(int semid, asmlinkage long sys_oabi_semtimedop(int semid,
struct oabi_sembuf __user *tsops, struct oabi_sembuf __user *tsops,
unsigned nsops, unsigned nsops,
const struct old_timespec32 __user *timeout) const struct old_timespec32 __user *timeout)
{ {
struct ipc_namespace *ns;
struct sembuf *sops; struct sembuf *sops;
struct old_timespec32 local_timeout;
long err; long err;
int i; int i;
ns = current->nsproxy->ipc_ns;
if (nsops > ns->sc_semopm)
return -E2BIG;
if (nsops < 1 || nsops > SEMOPM) if (nsops < 1 || nsops > SEMOPM)
return -EINVAL; return -EINVAL;
if (!access_ok(tsops, sizeof(*tsops) * nsops)) sops = kvmalloc_array(nsops, sizeof(*sops), GFP_KERNEL);
return -EFAULT;
sops = kmalloc_array(nsops, sizeof(*sops), GFP_KERNEL);
if (!sops) if (!sops)
return -ENOMEM; return -ENOMEM;
err = 0; err = 0;
for (i = 0; i < nsops; i++) { for (i = 0; i < nsops; i++) {
struct oabi_sembuf osb; struct oabi_sembuf osb;
err |= __copy_from_user(&osb, tsops, sizeof(osb)); err |= copy_from_user(&osb, tsops, sizeof(osb));
sops[i].sem_num = osb.sem_num; sops[i].sem_num = osb.sem_num;
sops[i].sem_op = osb.sem_op; sops[i].sem_op = osb.sem_op;
sops[i].sem_flg = osb.sem_flg; sops[i].sem_flg = osb.sem_flg;
tsops++; tsops++;
} }
if (timeout) {
/* copy this as well before changing domain protection */
err |= copy_from_user(&local_timeout, timeout, sizeof(*timeout));
timeout = &local_timeout;
}
if (err) { if (err) {
err = -EFAULT; err = -EFAULT;
} else { goto out;
mm_segment_t fs = get_fs(); }
set_fs(KERNEL_DS);
err = sys_semtimedop_time32(semid, sops, nsops, timeout); if (timeout) {
set_fs(fs); struct timespec64 ts;
err = get_old_timespec32(&ts, timeout);
if (err)
goto out;
err = __do_semtimedop(semid, sops, nsops, &ts, ns);
goto out;
} }
kfree(sops); err = __do_semtimedop(semid, sops, nsops, NULL, ns);
out:
kvfree(sops);
return err; return err;
} }
...@@ -368,6 +375,27 @@ asmlinkage int sys_oabi_ipc(uint call, int first, int second, int third, ...@@ -368,6 +375,27 @@ asmlinkage int sys_oabi_ipc(uint call, int first, int second, int third,
return sys_ipc(call, first, second, third, ptr, fifth); return sys_ipc(call, first, second, third, ptr, fifth);
} }
} }
#else
asmlinkage long sys_oabi_semtimedop(int semid,
struct oabi_sembuf __user *tsops,
unsigned nsops,
const struct old_timespec32 __user *timeout)
{
return -ENOSYS;
}
asmlinkage long sys_oabi_semop(int semid, struct oabi_sembuf __user *tsops,
unsigned nsops)
{
return -ENOSYS;
}
asmlinkage int sys_oabi_ipc(uint call, int first, int second, int third,
void __user *ptr, long fifth)
{
return -ENOSYS;
}
#endif
asmlinkage long sys_oabi_bind(int fd, struct sockaddr __user *addr, int addrlen) asmlinkage long sys_oabi_bind(int fd, struct sockaddr __user *addr, int addrlen)
{ {
......
...@@ -1373,6 +1373,9 @@ long ksys_old_shmctl(int shmid, int cmd, struct shmid_ds __user *buf); ...@@ -1373,6 +1373,9 @@ long ksys_old_shmctl(int shmid, int cmd, struct shmid_ds __user *buf);
long compat_ksys_semtimedop(int semid, struct sembuf __user *tsems, long compat_ksys_semtimedop(int semid, struct sembuf __user *tsems,
unsigned int nsops, unsigned int nsops,
const struct old_timespec32 __user *timeout); const struct old_timespec32 __user *timeout);
long __do_semtimedop(int semid, struct sembuf *tsems, unsigned int nsops,
const struct timespec64 *timeout,
struct ipc_namespace *ns);
int __sys_getsockopt(int fd, int level, int optname, char __user *optval, int __sys_getsockopt(int fd, int level, int optname, char __user *optval,
int __user *optlen); int __user *optlen);
......
...@@ -1984,46 +1984,34 @@ static struct sem_undo *find_alloc_undo(struct ipc_namespace *ns, int semid) ...@@ -1984,46 +1984,34 @@ static struct sem_undo *find_alloc_undo(struct ipc_namespace *ns, int semid)
return un; return un;
} }
static long do_semtimedop(int semid, struct sembuf __user *tsops, long __do_semtimedop(int semid, struct sembuf *sops,
unsigned nsops, const struct timespec64 *timeout) unsigned nsops, const struct timespec64 *timeout,
struct ipc_namespace *ns)
{ {
int error = -EINVAL; int error = -EINVAL;
struct sem_array *sma; struct sem_array *sma;
struct sembuf fast_sops[SEMOPM_FAST]; struct sembuf *sop;
struct sembuf *sops = fast_sops, *sop;
struct sem_undo *un; struct sem_undo *un;
int max, locknum; int max, locknum;
bool undos = false, alter = false, dupsop = false; bool undos = false, alter = false, dupsop = false;
struct sem_queue queue; struct sem_queue queue;
unsigned long dup = 0, jiffies_left = 0; unsigned long dup = 0, jiffies_left = 0;
struct ipc_namespace *ns;
ns = current->nsproxy->ipc_ns;
if (nsops < 1 || semid < 0) if (nsops < 1 || semid < 0)
return -EINVAL; return -EINVAL;
if (nsops > ns->sc_semopm) if (nsops > ns->sc_semopm)
return -E2BIG; return -E2BIG;
if (nsops > SEMOPM_FAST) {
sops = kvmalloc_array(nsops, sizeof(*sops), GFP_KERNEL);
if (sops == NULL)
return -ENOMEM;
}
if (copy_from_user(sops, tsops, nsops * sizeof(*tsops))) {
error = -EFAULT;
goto out_free;
}
if (timeout) { if (timeout) {
if (timeout->tv_sec < 0 || timeout->tv_nsec < 0 || if (timeout->tv_sec < 0 || timeout->tv_nsec < 0 ||
timeout->tv_nsec >= 1000000000L) { timeout->tv_nsec >= 1000000000L) {
error = -EINVAL; error = -EINVAL;
goto out_free; goto out;
} }
jiffies_left = timespec64_to_jiffies(timeout); jiffies_left = timespec64_to_jiffies(timeout);
} }
max = 0; max = 0;
for (sop = sops; sop < sops + nsops; sop++) { for (sop = sops; sop < sops + nsops; sop++) {
unsigned long mask = 1ULL << ((sop->sem_num) % BITS_PER_LONG); unsigned long mask = 1ULL << ((sop->sem_num) % BITS_PER_LONG);
...@@ -2052,7 +2040,7 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops, ...@@ -2052,7 +2040,7 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops,
un = find_alloc_undo(ns, semid); un = find_alloc_undo(ns, semid);
if (IS_ERR(un)) { if (IS_ERR(un)) {
error = PTR_ERR(un); error = PTR_ERR(un);
goto out_free; goto out;
} }
} else { } else {
un = NULL; un = NULL;
...@@ -2063,25 +2051,25 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops, ...@@ -2063,25 +2051,25 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops,
if (IS_ERR(sma)) { if (IS_ERR(sma)) {
rcu_read_unlock(); rcu_read_unlock();
error = PTR_ERR(sma); error = PTR_ERR(sma);
goto out_free; goto out;
} }
error = -EFBIG; error = -EFBIG;
if (max >= sma->sem_nsems) { if (max >= sma->sem_nsems) {
rcu_read_unlock(); rcu_read_unlock();
goto out_free; goto out;
} }
error = -EACCES; error = -EACCES;
if (ipcperms(ns, &sma->sem_perm, alter ? S_IWUGO : S_IRUGO)) { if (ipcperms(ns, &sma->sem_perm, alter ? S_IWUGO : S_IRUGO)) {
rcu_read_unlock(); rcu_read_unlock();
goto out_free; goto out;
} }
error = security_sem_semop(&sma->sem_perm, sops, nsops, alter); error = security_sem_semop(&sma->sem_perm, sops, nsops, alter);
if (error) { if (error) {
rcu_read_unlock(); rcu_read_unlock();
goto out_free; goto out;
} }
error = -EIDRM; error = -EIDRM;
...@@ -2095,7 +2083,7 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops, ...@@ -2095,7 +2083,7 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops,
* entangled here and why it's RMID race safe on comments at sem_lock() * entangled here and why it's RMID race safe on comments at sem_lock()
*/ */
if (!ipc_valid_object(&sma->sem_perm)) if (!ipc_valid_object(&sma->sem_perm))
goto out_unlock_free; goto out_unlock;
/* /*
* semid identifiers are not unique - find_alloc_undo may have * semid identifiers are not unique - find_alloc_undo may have
* allocated an undo structure, it was invalidated by an RMID * allocated an undo structure, it was invalidated by an RMID
...@@ -2104,7 +2092,7 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops, ...@@ -2104,7 +2092,7 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops,
* "un" itself is guaranteed by rcu. * "un" itself is guaranteed by rcu.
*/ */
if (un && un->semid == -1) if (un && un->semid == -1)
goto out_unlock_free; goto out_unlock;
queue.sops = sops; queue.sops = sops;
queue.nsops = nsops; queue.nsops = nsops;
...@@ -2130,10 +2118,10 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops, ...@@ -2130,10 +2118,10 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops,
rcu_read_unlock(); rcu_read_unlock();
wake_up_q(&wake_q); wake_up_q(&wake_q);
goto out_free; goto out;
} }
if (error < 0) /* non-blocking error path */ if (error < 0) /* non-blocking error path */
goto out_unlock_free; goto out_unlock;
/* /*
* We need to sleep on this operation, so we put the current * We need to sleep on this operation, so we put the current
...@@ -2198,14 +2186,14 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops, ...@@ -2198,14 +2186,14 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops,
if (error != -EINTR) { if (error != -EINTR) {
/* see SEM_BARRIER_2 for purpose/pairing */ /* see SEM_BARRIER_2 for purpose/pairing */
smp_acquire__after_ctrl_dep(); smp_acquire__after_ctrl_dep();
goto out_free; goto out;
} }
rcu_read_lock(); rcu_read_lock();
locknum = sem_lock(sma, sops, nsops); locknum = sem_lock(sma, sops, nsops);
if (!ipc_valid_object(&sma->sem_perm)) if (!ipc_valid_object(&sma->sem_perm))
goto out_unlock_free; goto out_unlock;
/* /*
* No necessity for any barrier: We are protect by sem_lock() * No necessity for any barrier: We are protect by sem_lock()
...@@ -2217,7 +2205,7 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops, ...@@ -2217,7 +2205,7 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops,
* Leave without unlink_queue(), but with sem_unlock(). * Leave without unlink_queue(), but with sem_unlock().
*/ */
if (error != -EINTR) if (error != -EINTR)
goto out_unlock_free; goto out_unlock;
/* /*
* If an interrupt occurred we have to clean up the queue. * If an interrupt occurred we have to clean up the queue.
...@@ -2228,13 +2216,45 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops, ...@@ -2228,13 +2216,45 @@ static long do_semtimedop(int semid, struct sembuf __user *tsops,
unlink_queue(sma, &queue); unlink_queue(sma, &queue);
out_unlock_free: out_unlock:
sem_unlock(sma, locknum); sem_unlock(sma, locknum);
rcu_read_unlock(); rcu_read_unlock();
out:
return error;
}
static long do_semtimedop(int semid, struct sembuf __user *tsops,
unsigned nsops, const struct timespec64 *timeout)
{
struct sembuf fast_sops[SEMOPM_FAST];
struct sembuf *sops = fast_sops;
struct ipc_namespace *ns;
int ret;
ns = current->nsproxy->ipc_ns;
if (nsops > ns->sc_semopm)
return -E2BIG;
if (nsops < 1)
return -EINVAL;
if (nsops > SEMOPM_FAST) {
sops = kvmalloc_array(nsops, sizeof(*sops), GFP_KERNEL);
if (sops == NULL)
return -ENOMEM;
}
if (copy_from_user(sops, tsops, nsops * sizeof(*tsops))) {
ret = -EFAULT;
goto out_free;
}
ret = __do_semtimedop(semid, sops, nsops, timeout, ns);
out_free: out_free:
if (sops != fast_sops) if (sops != fast_sops)
kvfree(sops); kvfree(sops);
return error;
return ret;
} }
long ksys_semtimedop(int semid, struct sembuf __user *tsops, long ksys_semtimedop(int semid, struct sembuf __user *tsops,
......
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