1 /* Basic tests for Linux SYSV shared memory extensions.
2    Copyright (C) 2020-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 #include <sys/ipc.h>
20 #include <sys/shm.h>
21 #include <errno.h>
22 #include <stdlib.h>
23 #include <stdbool.h>
24 #include <stdio.h>
25 #include <unistd.h>
26 #include <inttypes.h>
27 #include <limits.h>
28 
29 #include <support/check.h>
30 #include <support/temp_file.h>
31 
32 #define SHM_MODE 0644
33 
34 /* These are for the temporary file we generate.  */
35 static char *name;
36 static int shmid;
37 static long int pgsz;
38 
39 static void
remove_shm(void)40 remove_shm (void)
41 {
42   /* Enforce message queue removal in case of early test failure.
43      Ignore error since the shm may already have being removed.  */
44   shmctl (shmid, IPC_RMID, NULL);
45 }
46 
47 static void
do_prepare(int argc,char * argv[])48 do_prepare (int argc, char *argv[])
49 {
50   TEST_VERIFY_EXIT (create_temp_file ("tst-sysvshm.", &name) != -1);
51 }
52 
53 #define PREPARE do_prepare
54 
55 struct test_shminfo
56 {
57   __syscall_ulong_t shmall;
58   __syscall_ulong_t shmmax;
59   __syscall_ulong_t shmmni;
60 };
61 
62 /* It tries to obtain some system-wide SysV shared memory information from
63    /proc to check against IPC_INFO/SHM_INFO.  The /proc only returns the
64    tunables value of SHMALL, SHMMAX, and SHMMNI.  */
65 
66 static uint64_t
read_proc_file(const char * file)67 read_proc_file (const char *file)
68 {
69   FILE *f = fopen (file, "r");
70   if (f == NULL)
71     FAIL_UNSUPPORTED ("/proc is not mounted or %s is not available", file);
72 
73   /* Handle 32-bit binaries running on 64-bit kernels.  */
74   uint64_t v;
75   int r = fscanf (f, "%" SCNu64, &v);
76   TEST_VERIFY_EXIT (r == 1);
77 
78   fclose (f);
79   return v;
80 }
81 
82 
83 /* Check if the message queue with IDX (index into the kernel's internal
84    array) matches the one with KEY.  The CMD is either SHM_STAT or
85    SHM_STAT_ANY.  */
86 
87 static bool
check_shminfo(int idx,key_t key,int cmd)88 check_shminfo (int idx, key_t key, int cmd)
89 {
90   struct shmid_ds shminfo;
91   int sid = shmctl (idx, cmd, &shminfo);
92   /* Ignore unused array slot returned by the kernel or information from
93      unknown message queue.  */
94   if ((sid == -1 && errno == EINVAL) || sid != shmid)
95     return false;
96 
97   if (sid == -1)
98     FAIL_EXIT1 ("shmctl with %s failed: %m",
99 		cmd == SHM_STAT ? "SHM_STAT" : "SHM_STAT_ANY");
100 
101   TEST_COMPARE (shminfo.shm_perm.__key, key);
102   TEST_COMPARE (shminfo.shm_perm.mode, SHM_MODE);
103   TEST_COMPARE (shminfo.shm_segsz, pgsz);
104 
105   return true;
106 }
107 
108 static int
do_test(void)109 do_test (void)
110 {
111   atexit (remove_shm);
112 
113   pgsz = sysconf (_SC_PAGESIZE);
114   if (pgsz == -1)
115     FAIL_EXIT1 ("sysconf (_SC_PAGESIZE) failed: %m");
116 
117   key_t key = ftok (name, 'G');
118   if (key == -1)
119     FAIL_EXIT1 ("ftok failed: %m");
120 
121   shmid = shmget (key, pgsz, IPC_CREAT | IPC_EXCL | SHM_MODE);
122   if (shmid == -1)
123     FAIL_EXIT1 ("shmget failed: %m");
124 
125   /* It does not check shmmax because kernel clamp its value to INT_MAX for:
126 
127      1. Compat symbols with IPC_64, i.e, 32-bit binaries running on 64-bit
128         kernels.
129 
130      2. Default symbol without IPC_64 (defined as IPC_OLD within Linux) and
131         glibc always use IPC_64 for 32-bit ABIs (to support 64-bit time_t).
132         It means that 32-bit binaries running on 32-bit kernels will not see
133         shmmax being clamped.
134 
135      And finding out whether the compat symbol is used would require checking
136      the underlying kernel against the current ABI.  The shmall and shmmni
137      already provided enough coverage.  */
138 
139   struct test_shminfo tipcinfo;
140   tipcinfo.shmall = read_proc_file ("/proc/sys/kernel/shmall");
141   tipcinfo.shmmni = read_proc_file ("/proc/sys/kernel/shmmni");
142 
143   int shmidx;
144 
145   /* Note: SHM_INFO does not return a shminfo, but rather a 'struct shm_info'.
146      It is tricky to verify its values since the syscall returns system wide
147      resources consumed by shared memory.  The shmctl implementation handles
148      SHM_INFO as IPC_INFO, so the IPC_INFO test should validate SHM_INFO as
149      well.  */
150 
151   {
152     struct shminfo ipcinfo;
153     shmidx = shmctl (shmid, IPC_INFO, (struct shmid_ds *) &ipcinfo);
154     if (shmidx == -1)
155       FAIL_EXIT1 ("shmctl with IPC_INFO failed: %m");
156 
157     TEST_COMPARE (ipcinfo.shmall, tipcinfo.shmall);
158     TEST_COMPARE (ipcinfo.shmmni, tipcinfo.shmmni);
159   }
160 
161   /* We check if the created shared memory shows in the global list.  */
162   bool found = false;
163   for (int i = 0; i <= shmidx; i++)
164     {
165       /* We can't tell apart if SHM_STAT_ANY is not supported (kernel older
166 	 than 4.17) or if the index used is invalid.  So it just check if
167 	 value returned from a valid call matches the created message
168 	 queue.  */
169       check_shminfo (i, key, SHM_STAT_ANY);
170 
171       if (check_shminfo (i, key, SHM_STAT))
172 	{
173 	  found = true;
174 	  break;
175 	}
176     }
177 
178   if (!found)
179     FAIL_EXIT1 ("shmctl with SHM_STAT/SHM_STAT_ANY could not find the "
180 		"created shared memory");
181 
182   if (shmctl (shmid, IPC_RMID, NULL) == -1)
183     FAIL_EXIT1 ("shmctl failed");
184 
185   return 0;
186 }
187 
188 #include <support/test-driver.c>
189