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
49static int sem_id = -1;
50static uid_t nobody_uid, root_uid;
51static 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 */
57static 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
72static 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
84static 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
92static 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
134static 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
182static 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
210static 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
218static 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