1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) 2020 Cyril Hrubis <chrubis@suse.cz>
4 */
5
6 /*\
7 * [Description]
8 *
9 * Call shmctl() with SHM_INFO flag and check that:
10 *
11 * * The returned index points to a valid SHM by calling SHM_STAT_ANY
12 * * Also count that valid indexes < returned max index sums up to used_ids
13 * * And the data are consistent with /proc/sysvipc/shm
14 *
15 * There is a possible race between the call to the shmctl() and read from the
16 * proc file so this test cannot be run in parallel with any IPC testcases that
17 * adds or removes SHM segments.
18 *
19 * Note what we create a SHM segment in the test setup to make sure that there
20 * is at least one during the testrun.
21 */
22
23 #define _GNU_SOURCE
24 #include <stdio.h>
25 #include <pwd.h>
26 #include "tst_test.h"
27 #include "tst_safe_sysv_ipc.h"
28 #include "libnewipc.h"
29 #include "lapi/shm.h"
30
31 #define SHM_SIZE 2048
32
33 static int shm_id = -1;
34 static uid_t nobody_uid, root_uid;
35
36 static struct tcases {
37 uid_t *uid;
38 char *desc;
39 } tests[] = {
40 {&nobody_uid, "with nobody user"},
41 {&root_uid, "with root user"}
42 };
43
parse_proc_sysvipc(struct shm_info * info)44 static void parse_proc_sysvipc(struct shm_info *info)
45 {
46 int page_size = getpagesize();
47 FILE *f = fopen("/proc/sysvipc/shm", "r");
48 int used_ids = 0;
49 int shmid_max = 0;
50 unsigned long shm_rss = 0;
51 unsigned long shm_swp = 0;
52 unsigned long shm_tot = 0;
53
54 /* Eat header */
55 for (;;) {
56 int c = fgetc(f);
57
58 if (c == '\n' || c == EOF)
59 break;
60 }
61
62 int shmid, size, rss, swap;
63
64 /*
65 * Sum rss, swap and size for all elements listed, which should equal
66 * the data returned in the shm_info structure.
67 *
68 * Note that the size has to be rounded up to nearest multiple of page
69 * size.
70 */
71 while (fscanf(f, "%*i %i %*i %i %*i %*i %*i %*i %*i %*i %*i %*i %*i %*i %i %i",
72 &shmid, &size, &rss, &swap) > 0) {
73 used_ids++;
74 shm_rss += rss/page_size;
75 shm_swp += swap/page_size;
76 shm_tot += (size + page_size - 1) / page_size;
77 if (shmid > shmid_max)
78 shmid_max = shmid;
79 }
80
81 if (info->used_ids != used_ids) {
82 tst_res(TFAIL, "used_ids = %i, expected %i",
83 info->used_ids, used_ids);
84 } else {
85 tst_res(TPASS, "used_ids = %i", used_ids);
86 }
87
88 if (info->shm_rss != shm_rss) {
89 tst_res(TFAIL, "shm_rss = %li, expected %li",
90 info->shm_rss, shm_rss);
91 } else {
92 tst_res(TPASS, "shm_rss = %li", shm_rss);
93 }
94
95 if (info->shm_swp != shm_swp) {
96 tst_res(TFAIL, "shm_swp = %li, expected %li",
97 info->shm_swp, shm_swp);
98 } else {
99 tst_res(TPASS, "shm_swp = %li", shm_swp);
100 }
101
102 if (info->shm_tot != shm_tot) {
103 tst_res(TFAIL, "shm_tot = %li, expected %li",
104 info->shm_tot, shm_tot);
105 } else {
106 tst_res(TPASS, "shm_tot = %li", shm_tot);
107 }
108
109 fclose(f);
110 }
111
verify_shminfo(unsigned int n)112 static void verify_shminfo(unsigned int n)
113 {
114 struct tcases *tc = &tests[n];
115 struct shm_info info;
116 struct shmid_ds ds;
117 int i, shmid, cnt = 0;
118
119 tst_res(TINFO, "Test SHM_STAT_ANY %s", tc->desc);
120
121 SAFE_SETEUID(*tc->uid);
122
123 TEST(shmctl(0, SHM_INFO, (struct shmid_ds *)&info));
124
125 if (TST_RET == -1) {
126 tst_res(TFAIL | TTERRNO, "shmctl(0, SHM_INFO, ...)");
127 return;
128 }
129
130 shmid = shmctl(TST_RET, SHM_STAT_ANY, &ds);
131
132 if (shmid == -1) {
133 tst_res(TFAIL | TERRNO, "SHM_INFO haven't returned a valid index");
134 } else {
135 tst_res(TPASS,
136 "SHM_INFO returned valid index %li maps to shmid %i",
137 TST_RET, shmid);
138 }
139
140 for (i = 0; i <= TST_RET; i++) {
141 if (shmctl(i, SHM_STAT_ANY, &ds) != -1)
142 cnt++;
143 }
144
145 if (cnt == info.used_ids) {
146 tst_res(TPASS, "Counted used = %i", cnt);
147 } else {
148 tst_res(TFAIL, "Counted used = %i, used_ids = %i",
149 cnt, info.used_ids);
150 }
151
152 parse_proc_sysvipc(&info);
153 }
154
setup(void)155 static void setup(void)
156 {
157 struct passwd *ltpuser = SAFE_GETPWNAM("nobody");
158 struct shmid_ds temp_ds;
159
160 nobody_uid = ltpuser->pw_uid;
161 root_uid = 0;
162
163 shm_id = SAFE_SHMGET(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | SHM_RW);
164
165 TEST(shmctl(shm_id, SHM_STAT_ANY, &temp_ds));
166 if (TST_RET == -1) {
167 if (TST_ERR == EINVAL)
168 tst_brk(TCONF, "kernel doesn't support SHM_STAT_ANY");
169 else
170 tst_brk(TBROK | TTERRNO,
171 "Current environment doesn't permit SHM_STAT_ANY");
172 }
173 }
174
cleanup(void)175 static void cleanup(void)
176 {
177 if (shm_id >= 0)
178 SAFE_SHMCTL(shm_id, IPC_RMID, NULL);
179 }
180
181 static struct tst_test test = {
182 .setup = setup,
183 .cleanup = cleanup,
184 .test = verify_shminfo,
185 .tcnt = ARRAY_SIZE(tests),
186 .needs_root = 1,
187 };
188