1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2020 FUJITSU LIMITED. All rights reserved.
4 * Author: Feiyu Zhu <zhufy.jy@cn.fujitsu.com>
5 */
6 /*\
7 * [Description]
8 *
9 * Call semctl() with SEM_INFO flag and check that:
10 *
11 * * The returned index points to a valid SEM by calling SEM_STAT_ANY
12 * * Also count that valid indexes < returned max index sums up to semusz
13 * * And the data are consistent with /proc/sysvipc/sem
14 *
15 * There is a possible race between the call to the semctl() and read from the
16 * proc file so this test cannot be run in parallel with any IPC testcases that
17 * adds or removes semaphore set.
18 *
19 * Note what we create a semaphore set in the test setup to make sure
20 * that there is at least one during the testrun.
21 *
22 * Also note that for SEM_INFO the members of the seminfo structure have
23 * completely different meaning than their names seems to suggest.
24 *
25 * We also calling semctl() directly by syscall(), because of a glibc bug:
26 *
27 * semctl SEM_STAT_ANY fails to pass the buffer specified by the caller
28 * to the kernel.
29 *
30 * https://sourceware.org/bugzilla/show_bug.cgi?id=26637
31 */
32
33 /*
34 * The glibc bug was fixed in:
35 *
36 * * commit 574500a108be1d2a6a0dc97a075c9e0a98371aba
37 * * Author: Dmitry V. Levin <ldv@altlinux.org>
38 * * Date: Tue, 29 Sep 2020 17:10:20 +0000 (14:10 -0300)
39 */
40
41 #include <stdio.h>
42 #include <pwd.h>
43 #include "tst_test.h"
44 #include "tst_safe_sysv_ipc.h"
45 #include "libnewipc.h"
46 #include "lapi/sem.h"
47 #include "lapi/syscalls.h"
48
49 static int sem_id = -1;
50 static uid_t nobody_uid, root_uid;
51 static union semun un;
52
53 /*
54 * Note: semctl man-pages may have wrong description. We should use sem_ds
55 * struct(un.buf) instead of seminfo struct(un.__buf).
56 */
do_semctl(int semid,int semnum,int cmd)57 static inline int do_semctl(int semid, int semnum, int cmd)
58 {
59 struct semid_ds info;
60
61 un.buf = &info;
62
63 switch (tst_variant) {
64 case 0:
65 return tst_syscall(__NR_semctl, semid, semnum, cmd, un);
66 case 1:
67 return semctl(semid, semnum, cmd, un);
68 }
69 return -1;
70 }
71
test_info(void)72 static void test_info(void)
73 {
74 switch (tst_variant) {
75 case 0:
76 tst_res(TINFO, "Test SYS_semctl syscall");
77 break;
78 case 1:
79 tst_res(TINFO, "Test libc semctl()");
80 break;
81 }
82 }
83
84 static struct tcases {
85 uid_t *uid;
86 char *desc;
87 } tests[] = {
88 {&nobody_uid, "with nobody user",},
89 {&root_uid, "with root user",},
90 };
91
parse_proc_sysvipc(struct seminfo * info)92 static void parse_proc_sysvipc(struct seminfo *info)
93 {
94 FILE *f = fopen("/proc/sysvipc/sem", "r");
95 int semset_cnt = 0;
96 int sem_cnt = 0;
97
98 /* Eat header */
99 for (;;) {
100 int c = fgetc(f);
101
102 if (c == '\n' || c == EOF)
103 break;
104 }
105
106 int nsems;
107 /*
108 * Sum sem set, nsems for all elements listed, which should equal
109 * the data returned in the seminfo structure.
110 */
111 while (fscanf(f, "%*i %*i %*i %i %*i %*i %*i %*i %*i %*i",
112 &nsems) > 0){
113 semset_cnt++;
114 sem_cnt += nsems;
115 }
116
117 if (info->semusz != semset_cnt) {
118 tst_res(TFAIL, "semusz = %i, expected %i",
119 info->semusz, semset_cnt);
120 } else {
121 tst_res(TPASS, "semset_cnt = %i", semset_cnt);
122 }
123
124 if (info->semaem != sem_cnt) {
125 tst_res(TFAIL, "semaem = %i, expected %i",
126 info->semaem, sem_cnt);
127 } else {
128 tst_res(TPASS, "sen_cnt = %i", sem_cnt);
129 }
130
131 fclose(f);
132 }
133
verify_semctl(unsigned int n)134 static void verify_semctl(unsigned int n)
135 {
136 struct tcases *tc = &tests[n];
137 int i, semid, cnt = 0;
138 struct seminfo info;
139 union semun arg;
140
141 tst_res(TINFO, "Test SEM_STAT_ANY %s", tc->desc);
142
143 SAFE_SETEUID(*tc->uid);
144
145 arg.__buf = &info;
146
147 TEST(semctl(sem_id, 0, SEM_INFO, arg));
148
149 if (TST_RET == -1) {
150 tst_res(TFAIL | TTERRNO, "semctl(sem_id, 0, SEM_INFO, ...)");
151 return;
152 }
153
154 semid = do_semctl(TST_RET, 0, SEM_STAT_ANY);
155
156 if (errno == EFAULT) {
157 tst_res(TFAIL, "SEM_STAT_ANY doesn't pass the buffer "
158 "specified by the caller to kernel");
159 return;
160 } else if (semid == -1) {
161 tst_res(TFAIL | TERRNO, "SEM_INFO haven't returned a valid index");
162 } else {
163 tst_res(TPASS, "SEM_INFO returned valid index %li to semid %i",
164 TST_RET, semid);
165 }
166
167 for (i = 0; i <= TST_RET; i++) {
168 if ((do_semctl(i, 0, SEM_STAT_ANY)) != -1)
169 cnt++;
170 }
171
172 if (cnt == info.semusz) {
173 tst_res(TPASS, "Counted used = %i", cnt);
174 } else {
175 tst_res(TFAIL, "Counted used = %i, semuse = %i",
176 cnt, info.semusz);
177 }
178
179 parse_proc_sysvipc(&info);
180 }
181
setup(void)182 static void setup(void)
183 {
184 struct passwd *ltpuser = SAFE_GETPWNAM("nobody");
185
186 nobody_uid = ltpuser->pw_uid;
187 root_uid = 0;
188 test_info();
189
190 #if !HAVE_DECL_SEM_STAT_ANY
191 if (tst_variant == 1)
192 tst_brk(TCONF, "libc does not support semctl(SEM_STAT_ANY)");
193 #endif
194
195 sem_id = SAFE_SEMGET(IPC_PRIVATE, 2, IPC_CREAT | 0600);
196
197 TEST(do_semctl(sem_id, 0, SEM_STAT_ANY));
198 if (TST_RET == -1) {
199 if (TST_ERR == EFAULT)
200 tst_brk(TFAIL,
201 "SEM_STAT_ANY doesn't pass the buffer specified by the caller to kernel");
202 if (TST_ERR == EINVAL)
203 tst_brk(TCONF, "kernel doesn't support SEM_STAT_ANY");
204 else
205 tst_brk(TBROK | TTERRNO,
206 "Current environment doesn't permit SEM_STAT_ANY");
207 }
208 }
209
cleanup(void)210 static void cleanup(void)
211 {
212 SAFE_SETEUID(root_uid);
213
214 if (sem_id >= 0)
215 SAFE_SEMCTL(sem_id, 0, IPC_RMID);
216 }
217
218 static struct tst_test test = {
219 .setup = setup,
220 .cleanup = cleanup,
221 .test = verify_semctl,
222 .tcnt = ARRAY_SIZE(tests),
223 .test_variants = 2,
224 .needs_root = 1,
225 .tags = (const struct tst_tag[]) {
226 {"glibc-git", "574500a108be"},
227 {}
228 }
229 };
230