sem: test semctl's command instead of masking its bits
The time64 fixup for sem_otime/sem_ctime was guarded with
if ((cmd & (IPC_STAT | IPC_SET)) && (arg.__pad != NULL))
IPC_SET is 1 and IPC_STAT is 2, so the mask is 3: the condition asks
whether either of the low two bits of cmd is set, not whether cmd is one of
the two. It is therefore also true for IPC_INFO (3), GETPID (11),
GETALL (13), GETNCNT (14), GETZCNT (15), SETALL (17) and SEM_INFO (19).
GETVAL (12) and SETVAL (16) escape only because their low bits happen to be
zero.
For those commands the fourth argument is not a struct semid_ds. In the
32-bit time64 layout struct ipc_perm is 36 bytes and sem_otime/sem_ctime sit
at offsets 56 and 64 of the 80-byte struct semid_ds, so the two assignments
put 16 bytes at offset 56 of whatever was passed: an unsigned short array
for GETALL and SETALL, a 40-byte struct seminfo for the two INFO commands,
and for GETPID, GETNCNT and GETZCNT a pointer the caller never passed at
all, so whatever a leftover register holds.
msgctl() and shmctl() perform the same recombination but take a typed
pointer, so a caller cannot hand them another type. semctl(), with its
varargs union semun, is the only one of the three that has to look at cmd.
IPC_SET is dropped rather than compared: there the kernel only reads the
buffer, and recomputing sem_otime/sem_ctime from halves it did not touch
changes nothing. SEM_STAT is added, which the mask caught by accident and
which does return a struct semid_ds.
Confirmed by tst-semctl in uclibc-ng-test, extended for this: 14 CI targets
failed on it, in both byte orders -- on riscv32 elements 28..35 of a
40 semaphore array came back as 19..26, on m68k as 21,22,19,20,25,26,23,24,
the two words swapped -- and IPC_INFO wrote past its seminfo. With this
change the test passes on i686 with UCLIBC_USE_TIME64.
Signed-off-by: Ramin Moussavi <lordrasmus@gmail.com>