Browse Source

sparc64: pass the semun value to ipc(), not a pointer to it

sem and tst-semctl fail on sparc64 when uClibc-ng is built against
headers older than 5.1: semctl(IPC_STAT) returns 0 but the caller's
structure is untouched, so the test prints sem_perm.uid as -4898980,
sem_nsems as 0 and a time somewhere in year ????.

sparc64 is the only architecture in the kernel with its own SysV IPC
demultiplexer, sys_sparc_ipc() in arch/sparc/kernel/sys_sparc_64.c
("No need for backward compatibility.  We can start fresh..."), wired at
215 in systbls_64.S.  For SEMCTL it does

	err = sys_semctl(first, second, (int)third | IPC_64,
			 (unsigned long) ptr);

where the generic sys_ipc() reads the argument through ptr with
get_user() first.  So on sparc64 ptr *is* the semun value, and passing
&arg makes the kernel write the 88-byte semid_ds over the 8-byte union
on our own stack -- the caller's buffer never sees anything.  It also
ORs IPC_64 in itself, so the bit is irrelevant there.

Handing the union over as one pointer-sized word is what the kernel is
written for, not a trick of ours: ipc/sem.c reads the SETVAL argument back
with "val = arg >> 32" under CONFIG_64BIT && __BIG_ENDIAN, which is where
userspace's arg.val sits once the union is passed by value.  arg.__pad is
the void * member of union semun, and the direct-syscall path in this same
file already passes it for that reason.

Everyone else -- arm, m68k, mips, powerpc, s390, sh, sparc32 and x86 --
uses the generic multiplexer, and so does the 32-bit compat path on a
sparc64 kernel (compat_sys_ipc), so &arg stays right for them.  msgctl()
and shmctl() are unaffected in either case: they hand the buffer over
directly, the extra indirection exists only because semctl() takes a
union.

Invisible from 5.1 on, where __NR_semctl exists and the multiplexer is
never entered -- which is why the 6.5.10 target passes and the 4.19.56
one does not.  The line dates back to the original import, so any
sparc64 build against older headers has been corrupting its stack in
semctl() all along.

Verified A/B under qemu-system-sparc64 with 4.19.56 headers, same kernel
and boot, both binaries linked against their intended libc.a (checked
with -Wl,-t): without the fix sem_nsems reads 0 and the test exits 1,
with it sem_nsems is 1 and the test exits 0.

Signed-off-by: Ramin Moussavi <lordrasmus@gmail.com>
ramin 1 week ago
parent
commit
881175f30a
1 changed files with 10 additions and 0 deletions
  1. 10 0
      libc/misc/sysvipc/sem.c

+ 10 - 0
libc/misc/sysvipc/sem.c

@@ -68,7 +68,17 @@ int semctl(int semid, int semnum, int cmd, ...)
 #endif
     return __ret;
 #else
+# if defined __sparc__ && defined __arch64__
+    /* sparc64 has its own demultiplexer, sys_sparc_ipc() in
+       arch/sparc/kernel/sys_sparc_64.c: for SEMCTL it hands ptr straight to
+       sys_semctl() as the semun argument, where the generic sys_ipc() reads
+       the argument through it with get_user().  Passing &arg there makes the
+       kernel write the semid_ds over the union itself -- and it sets IPC_64
+       on its own, so the bit does not matter.  */
+    return __syscall_ipc(IPCOP_semctl, semid, semnum, cmd, arg.__pad, NULL);
+# else
     return __syscall_ipc(IPCOP_semctl, semid, semnum, cmd|__IPC_64, &arg, NULL);
+# endif
 #endif
 }
 #endif