1
+ // Source: https://github.com/snapcore/snapcraft-preloads/blob/master/semaphores/preload-semaphores.c
2
+
3
+ #ifndef _GNU_SOURCE
4
+ #define _GNU_SOURCE
5
+ #endif
6
+
7
+ /*
8
+ * $ gcc -Wall -fPIC -shared -o mylib.so ./lib.c -ldl
9
+ * $ LD_PRELOAD=./mylib.so ...
10
+ */
11
+
12
+ #include <dlfcn.h>
13
+
14
+ #include <sys/types.h>
15
+ #include <stdio.h>
16
+ #include <semaphore.h>
17
+ #include <string.h>
18
+ #include <stdlib.h>
19
+ #include <errno.h>
20
+ #include <sys/stat.h>
21
+ #include <unistd.h>
22
+ #include <fcntl.h>
23
+ #include <stdarg.h>
24
+ #include <limits.h>
25
+
26
+ static sem_t * (* original_sem_open ) (const char * , int , ...);
27
+ static int (* original_sem_unlink ) (const char * );
28
+
29
+ // Format is: 'sem.snap.SNAP_NAME.<something>'. So: 'sem.snap.' + '.' = 10
30
+ #define MAX_NAME_SIZE NAME_MAX - 10
31
+ #define SHM_DIR "/dev/shm"
32
+
33
+ void debug (char * s , ...)
34
+ {
35
+ if (secure_getenv ("SEMWRAP_DEBUG" )) {
36
+ va_list va ;
37
+ va_start (va , s );
38
+ fprintf (stderr , "SEMWRAP: " );
39
+ vfprintf (stderr , s , va );
40
+ va_end (va );
41
+ fprintf (stderr , "\n" );
42
+ }
43
+ }
44
+
45
+ const char * get_snap_name (void )
46
+ {
47
+ const char * snapname = getenv ("SNAP_INSTANCE_NAME" );
48
+ if (!snapname ) {
49
+ snapname = getenv ("SNAP_NAME" );
50
+ }
51
+ if (!snapname ) {
52
+ debug ("SNAP_NAME and SNAP_INSTANCE_NAME not set" );
53
+ }
54
+ return snapname ;
55
+ }
56
+
57
+ int rewrite (const char * snapname , const char * name , char * rewritten ,
58
+ size_t rmax )
59
+ {
60
+ if (strlen (snapname ) + strlen (name ) > MAX_NAME_SIZE ) {
61
+ errno = ENAMETOOLONG ;
62
+ return -1 ;
63
+ }
64
+
65
+ const char * tmp = name ;
66
+ if (tmp [0 ] == '/' ) {
67
+ // If specified with leading '/', just strip it to avoid
68
+ // having to mkdir(), etc
69
+ tmp = & name [1 ];
70
+ }
71
+
72
+ int n = snprintf (rewritten , rmax , "snap.%s.%s" , snapname , tmp );
73
+ if (n < 0 || n >= rmax ) {
74
+ fprintf (stderr , "snprintf truncated\n" );
75
+ return -1 ;
76
+ }
77
+ rewritten [rmax - 1 ] = '\0' ;
78
+
79
+ return 0 ;
80
+ }
81
+
82
+ sem_t * sem_open (const char * name , int oflag , ...)
83
+ {
84
+ mode_t mode ;
85
+ unsigned int value ;
86
+
87
+ debug ("sem_open()" );
88
+ debug ("requested name: %s" , name );
89
+
90
+ // lookup the libc's sem_open() if we haven't already
91
+ if (!original_sem_open ) {
92
+ dlerror ();
93
+ original_sem_open = dlsym (RTLD_NEXT , "sem_open" );
94
+ if (!original_sem_open ) {
95
+ debug ("could not find sem_open in libc" );
96
+ return SEM_FAILED ;
97
+ }
98
+ dlerror ();
99
+ }
100
+
101
+ // mode and value must be set with O_CREAT
102
+ va_list argp ;
103
+ va_start (argp , oflag );
104
+ if (oflag & O_CREAT ) {
105
+ mode = va_arg (argp , mode_t );
106
+ value = va_arg (argp , unsigned int );
107
+ if (value > SEM_VALUE_MAX ) {
108
+ errno = EINVAL ;
109
+ return SEM_FAILED ;
110
+ }
111
+ }
112
+ va_end (argp );
113
+
114
+ const char * snapname = get_snap_name ();
115
+
116
+ // just call libc's sem_open() if snapname not set
117
+ if (!snapname ) {
118
+ if (oflag & O_CREAT ) {
119
+ return original_sem_open (name , oflag , mode , value );
120
+ }
121
+ return original_sem_open (name , oflag );
122
+ }
123
+
124
+ // Format the rewritten name
125
+ char rewritten [MAX_NAME_SIZE + 1 ];
126
+ if (rewrite (snapname , name , rewritten , MAX_NAME_SIZE + 1 ) != 0 ) {
127
+ return SEM_FAILED ;
128
+ }
129
+ debug ("rewritten name: %s" , rewritten );
130
+
131
+ if (oflag & O_CREAT ) {
132
+ // glibc's sem_open with O_CREAT will create a file in /dev/shm
133
+ // by creating a tempfile, initializing it, hardlinking it and
134
+ // unlinking the tempfile. We:
135
+ // 1. create a temporary file in /dev/shm with rewritten path
136
+ // as the template and the specified mode
137
+ // 2. initializing a sem_t with sem_init
138
+ // 3. writing the initialized sem_t to the temporary file using
139
+ // sem_open()s declared value. We used '1' for pshared since
140
+ // that is how glibc sets up a named semaphore
141
+ // 4. close the temporary file
142
+ // 5. hard link the temporary file to the rewritten path. If
143
+ // O_EXCL is not specified, ignore EEXIST and just cleanup
144
+ // as per documented behavior in 'man sem_open'. If O_EXCL
145
+ // is specified and file exists, exit with error. If link is
146
+ // successful, cleanup.
147
+ // 6. call glibc's sem_open() without O_CREAT|O_EXCL
148
+ //
149
+ // See glibc's fbtl/sem_open.c for more details
150
+
151
+ // First, calculate the requested path
152
+ char path [PATH_MAX ] = { 0 };
153
+ // /sem. + '/0' = 14
154
+ int max_path_size = strlen (SHM_DIR ) + strlen (rewritten ) + 6 ;
155
+ if (max_path_size >= PATH_MAX ) {
156
+ // Should never happen since PATH_MAX should be much
157
+ // larger than NAME_MAX, but be defensive.
158
+ errno = ENAMETOOLONG ;
159
+ return SEM_FAILED ;
160
+ }
161
+ int n = snprintf (path , max_path_size , "%s/sem.%s" , SHM_DIR ,
162
+ rewritten );
163
+ if (n < 0 || n >= max_path_size ) {
164
+ errno = ENAMETOOLONG ;
165
+ return SEM_FAILED ;
166
+ }
167
+ path [max_path_size - 1 ] = '\0' ;
168
+
169
+ // Then calculate the template path
170
+ char tmp [PATH_MAX ] = { 0 };
171
+ n = snprintf (tmp , PATH_MAX , "%s/%s.XXXXXX" , SHM_DIR ,
172
+ rewritten );
173
+ if (n < 0 || n >= PATH_MAX ) {
174
+ errno = ENAMETOOLONG ;
175
+ return SEM_FAILED ;
176
+ }
177
+ tmp [PATH_MAX - 1 ] = '\0' ;
178
+
179
+ // Next, create a temporary file
180
+ int fd = mkstemp (tmp );
181
+ if (fd < 0 ) {
182
+ return SEM_FAILED ;
183
+ }
184
+ debug ("tmp name: %s" , tmp );
185
+
186
+ // Update the temporary file to have the requested mode
187
+ if (fchmod (fd , mode ) < 0 ) {
188
+ close (fd );
189
+ unlink (tmp );
190
+ return SEM_FAILED ;
191
+ }
192
+
193
+ // Then write out an empty semaphore and set the initial value.
194
+ // We use '1' for pshared since that is how glibc sets up the
195
+ // semaphore (see glibc's fbtl/sem_open.c)
196
+ sem_t initsem ;
197
+ sem_init (& initsem , 1 , value );
198
+ if (write (fd , & initsem , sizeof (sem_t )) < 0 ) {
199
+ close (fd );
200
+ unlink (tmp );
201
+ return SEM_FAILED ;
202
+ }
203
+ close (fd );
204
+
205
+ // Then link the file into place. If the target exists and
206
+ // O_EXCL was not specified, just cleanup and proceed to open
207
+ // the existing file as per documented behavior in 'man
208
+ // sem_open'.
209
+ int existed = 0 ;
210
+ if (link (tmp , path ) < 0 ) {
211
+ // Note: snapd initially didn't allow 'l' in its
212
+ // policy so we first try with link() since it is
213
+ // race-free but fallback to rename() if necessary.
214
+ if (errno == EACCES || errno == EPERM ) {
215
+ fprintf (stderr , "sem_open() wrapper: hard linking tempfile denied. Falling back to rename()\n" );
216
+ if (rename (tmp , path ) < 0 ) {
217
+ unlink (tmp );
218
+ return SEM_FAILED ;
219
+ }
220
+ } else if (oflag & O_EXCL || errno != EEXIST ) {
221
+ unlink (tmp );
222
+ return SEM_FAILED ;
223
+ }
224
+ existed = 1 ;
225
+ }
226
+ unlink (tmp );
227
+
228
+ // Then call sem_open() on the created file, stripping out the
229
+ // O_CREAT|O_EXCL since we just created it
230
+ sem_t * sem = original_sem_open (rewritten ,
231
+ oflag & ~(O_CREAT | O_EXCL ));
232
+ if (sem == SEM_FAILED ) {
233
+ if (!existed ) {
234
+ unlink (path );
235
+ }
236
+ return SEM_FAILED ;
237
+ }
238
+
239
+ return sem ;
240
+ } else {
241
+ // without O_CREAT, just call sem_open with rewritten
242
+ return original_sem_open (rewritten , oflag );
243
+ }
244
+
245
+ return SEM_FAILED ;
246
+ }
247
+
248
+ // sem_unlink
249
+ int sem_unlink (const char * name )
250
+ {
251
+ debug ("sem_unlink()" );
252
+ debug ("requested name: %s" , name );
253
+
254
+ // lookup the libc's sem_unlink() if we haven't already
255
+ if (!original_sem_unlink ) {
256
+ dlerror ();
257
+ original_sem_unlink = dlsym (RTLD_NEXT , "sem_unlink" );
258
+ if (!original_sem_unlink ) {
259
+ debug ("could not find sem_unlink in libc" );
260
+ return -1 ;
261
+ }
262
+ dlerror ();
263
+ }
264
+
265
+ const char * snapname = get_snap_name ();
266
+
267
+ // just call libc's sem_unlink() if snapname not set
268
+ if (!snapname ) {
269
+ return original_sem_unlink (name );
270
+ }
271
+
272
+ // Format the rewritten name
273
+ char rewritten [MAX_NAME_SIZE + 1 ];
274
+ if (rewrite (snapname , name , rewritten , MAX_NAME_SIZE + 1 ) != 0 ) {
275
+ return -1 ;
276
+ }
277
+ debug ("rewritten name: %s" , rewritten );
278
+
279
+ return original_sem_unlink (rewritten );
280
+ }
0 commit comments