Improves compatibility with AddressSanitizers
[src/app-framework-binder.git] / src / afb-api-so.c
1 /*
2  * Copyright (C) 2015-2020 "IoT.bzh"
3  * Author José Bollo <jose.bollo@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #if WITH_DYNAMIC_BINDING
19
20 #define _GNU_SOURCE
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <dlfcn.h>
25 #include <string.h>
26 #include <dirent.h>
27 #include <errno.h>
28 #include <sys/stat.h>
29
30 #include "afb-api-so.h"
31 #include "afb-api-so-v3.h"
32 #include "verbose.h"
33 #include "sig-monitor.h"
34
35 #if WITH_LEGACY_BINDING_V1
36 #   include "afb-api-so-v1.h"
37 #endif
38 #if WITH_LEGACY_BINDING_VDYN
39 #   include "afb-api-so-vdyn.h"
40 #endif
41 #if WITH_LEGACY_BINDING_V2
42 #   include "afb-api-so-v2.h"
43 #endif
44
45 struct safe_dlopen
46 {
47         const char *path;
48         void *handle;
49         int flags;
50 };
51
52 static void safe_dlopen_cb(int sig, void *closure)
53 {
54         struct safe_dlopen *sd = closure;
55         if (!sig)
56                 sd->handle = dlopen(sd->path, sd->flags);
57         else {
58                 ERROR("dlopen of %s raised signal %s", sd->path, strsignal(sig));
59                 sd->handle = NULL;
60         }
61 }
62
63 static void *safe_dlopen(const char *filename, int flags)
64 {
65         struct safe_dlopen sd;
66         sd.path = filename;
67         sd.flags = flags;
68         sd.handle = NULL;
69         sig_monitor(0, safe_dlopen_cb, &sd);
70         return sd.handle;
71 }
72
73 static int load_binding(const char *path, int force, struct afb_apiset *declare_set, struct afb_apiset * call_set)
74 {
75         int obsolete = 0;
76         int rc;
77         void *handle;
78         static int dlopen_flags = 0;
79
80         /* compute the dlopen flags */
81         if (dlopen_flags == 0) {
82                 /* For ASan mode, export AFB_NO_RTLD_DEEPBIND=1, to disable RTLD_DEEPBIND */
83                 char *string = secure_getenv("AFB_NO_RTLD_DEEPBIND");
84                 if (string && string[0] == '1' && string[1] == 0)
85                         dlopen_flags = RTLD_NOW | RTLD_LOCAL;
86                 else
87                         dlopen_flags = RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND;
88         }
89
90         /* set default return code rc according to force */
91         rc = -!!force;
92
93         /* try to open the library */
94         handle = safe_dlopen(path, dlopen_flags);
95         if (handle == NULL) {
96                 if (force)
97                         ERROR("binding [%s] not loadable: %s", path, dlerror());
98                 else
99                         WARNING("binding [%s] not loadable: %s", path, dlerror());
100                 goto error;
101         }
102         /*
103          * This is a loadable library let's check if it's a binding ...
104          */
105
106         /* try the version 3 */
107         rc = afb_api_so_v3_add(path, handle, declare_set, call_set);
108         if (rc < 0) {
109                 /* error when loading a valid v3 binding */
110                 goto error2;
111         }
112         if (rc)
113                 return 0; /* yes version 3 */
114
115 #if WITH_LEGACY_BINDING_V2
116         /* try the version 2 */
117         rc = afb_api_so_v2_add(path, handle, declare_set, call_set);
118         if (rc < 0) {
119                 /* error when loading a valid v2 binding */
120                 goto error2;
121         }
122         if (rc)
123                 return 0; /* yes version 2 */
124 #else
125         if (dlsym(handle, "afbBindingV2")) {
126                 WARNING("binding [%s]: version 2 not supported", path);
127                 obsolete = 1;
128         }
129 #endif
130
131 #if WITH_LEGACY_BINDING_VDYN
132         /* try the version dyn */
133         rc = afb_api_so_vdyn_add(path, handle, declare_set, call_set);
134         if (rc < 0) {
135                 /* error when loading a valid dyn binding */
136                 goto error2;
137         }
138         if (rc)
139                 return 0; /* yes version dyn */
140 #else
141         if (dlsym(handle, "afbBindingVdyn")) {
142                 WARNING("binding [%s]: version DYN not supported", path);
143                 obsolete = 1;
144         }
145 #endif
146
147 #if WITH_LEGACY_BINDING_V1
148         /* try the version 1 */
149         rc = afb_api_so_v1_add(path, handle, declare_set, call_set);
150         if (rc < 0) {
151                 /* error when loading a valid v1 binding */
152                 goto error2;
153         }
154         if (rc)
155                 return 0; /* yes version 1 */
156 #else
157         if (dlsym(handle, "afbBindingV1Register")) {
158                 WARNING("binding [%s]: version 1 not supported", path);
159                 obsolete = 1;
160         }
161 #endif
162
163         /* not a valid binding */
164         _VERBOSE_(force ? Log_Level_Error : Log_Level_Info, "binding [%s] %s",
165                         path, obsolete ? "is obsolete" : "isn't an AFB binding");
166
167 error2:
168         dlclose(handle);
169 error:
170         return rc;
171 }
172
173
174 int afb_api_so_add_binding(const char *path, struct afb_apiset *declare_set, struct afb_apiset * call_set)
175 {
176         return load_binding(path, 1, declare_set, call_set);
177 }
178
179 static int adddirs(char path[PATH_MAX], size_t end, struct afb_apiset *declare_set, struct afb_apiset * call_set, int failstops)
180 {
181         DIR *dir;
182         struct dirent *dent;
183         struct stat st;
184         size_t len;
185         int rc = 0;
186
187         /* open the DIR now */
188         dir = opendir(path);
189         if (dir == NULL) {
190                 ERROR("can't scan binding directory %s, %m", path);
191                 return -1;
192         }
193         INFO("Scanning dir=[%s] for bindings", path);
194
195         /* scan each entry */
196         if (end)
197                 path[end++] = '/';
198         for (;;) {
199                 errno = 0;
200                 dent = readdir(dir);
201                 if (dent == NULL) {
202                         if (errno != 0)
203                                 ERROR("read error while scanning directory %.*s: %m", (int)(end - 1), path);
204                         break;
205                 }
206
207                 /* get the name and inspect dereferenced link instead of the directory entry */
208                 len = strlen(dent->d_name);
209                 if (len + end >= PATH_MAX) {
210                         ERROR("path too long while scanning bindings for %.*s%s", (int)end, path, dent->d_name);
211                         continue;
212                 }
213                 memcpy(&path[end], dent->d_name, len+1);
214                 rc = stat(path, &st);
215                 if (rc < 0) {
216                         ERROR("getting status of %s failed: %m", path);
217                         continue;
218                 }
219                 else if (S_ISDIR(st.st_mode)) {
220                         /* case of directories */
221                         if (dent->d_name[0] == '.') {
222 /*
223 Exclude from the search of bindings any
224 directory starting with a dot (.) by default.
225
226 It is possible to reactivate the prvious behaviour
227 by defining the following preprocessor variables
228
229  - AFB_API_SO_ACCEPT_DOT_PREFIXED_DIRS
230
231    When this variable is defined, the directories
232    starting with a dot are searched except
233    if their name is "." or ".." or ".debug"
234
235  - AFB_API_SO_ACCEPT_DOT_DEBUG_DIRS
236
237    When this variable is defined and the variable
238    AFB_API_SO_ACCEPT_DOT_PREFIXED_DIRS is also defined
239    scans any directory not being "." or "..".
240
241 The previous behaviour was like difining the 2 variables,
242 meaning that only . and .. were excluded from the search.
243
244 This change is intended to definitely solve the issue
245 SPEC-662. Yocto installed the debugging symbols in the
246 subdirectory .debug. For example the binding.so also
247 had a .debug/binding.so file attached. Opening that
248 debug file made dlopen crashing.
249 See https://sourceware.org/bugzilla/show_bug.cgi?id=22101
250  */
251 #if !defined(AFB_API_SO_ACCEPT_DOT_PREFIXED_DIRS) /* not defined by default */
252                                 continue; /* ignore any directory beginning with a dot */
253 #else
254                                 if (len == 1)
255                                         continue; /* . */
256                                 if (dent->d_name[1] == '.' && len == 2)
257                                         continue; /* .. */
258 #if !defined(AFB_API_SO_ACCEPT_DOT_DEBUG_DIRS) /* not defined by default */
259                                 if (len == 6
260                                  && dent->d_name[1] == 'd'
261                                  && dent->d_name[2] == 'e'
262                                  && dent->d_name[3] == 'b'
263                                  && dent->d_name[4] == 'u'
264                                  && dent->d_name[5] == 'g')
265                                         continue; /* .debug */
266 #endif
267 #endif
268                         }
269                         rc = adddirs(path, end+len, declare_set, call_set, failstops);
270                 } else if (S_ISREG(st.st_mode)) {
271                         /* case of files */
272                         if (memcmp(&dent->d_name[len - 3], ".so", 4))
273                                 continue;
274                         rc = load_binding(path, 0, declare_set, call_set);
275                 }
276                 if (rc < 0 && failstops) {
277                         closedir(dir);
278                         return rc;
279                 }
280         }
281         closedir(dir);
282         return 0;
283 }
284
285 int afb_api_so_add_directory(const char *path, struct afb_apiset *declare_set, struct afb_apiset * call_set, int failstops)
286 {
287         size_t length;
288         char buffer[PATH_MAX];
289
290         length = strlen(path);
291         if (length >= sizeof(buffer)) {
292                 ERROR("path too long %lu [%.99s...]", (unsigned long)length, path);
293                 return -1;
294         }
295
296         memcpy(buffer, path, length + 1);
297         return adddirs(buffer, length, declare_set, call_set, failstops);
298 }
299
300 int afb_api_so_add_path(const char *path, struct afb_apiset *declare_set, struct afb_apiset * call_set, int failstops)
301 {
302         struct stat st;
303         int rc;
304
305         rc = stat(path, &st);
306         if (rc < 0)
307                 ERROR("Invalid binding path [%s]: %m", path);
308         else if (S_ISDIR(st.st_mode))
309                 rc = afb_api_so_add_directory(path, declare_set, call_set, failstops);
310         else if (strstr(path, ".so"))
311                 rc = load_binding(path, 0, declare_set, call_set);
312         else
313                 INFO("not a binding [%s], skipped", path);
314         return rc;
315 }
316
317 int afb_api_so_add_pathset(const char *pathset, struct afb_apiset *declare_set, struct afb_apiset * call_set, int failstops)
318 {
319         static char sep[] = ":";
320         char *ps, *p;
321         int rc;
322
323         ps = strdupa(pathset);
324         for (;;) {
325                 p = strsep(&ps, sep);
326                 if (!p)
327                         return 0;
328                 rc = afb_api_so_add_path(p, declare_set, call_set, failstops);
329                 if (rc < 0)
330                         return rc;
331         }
332 }
333
334 int afb_api_so_add_pathset_fails(const char *pathset, struct afb_apiset *declare_set, struct afb_apiset * call_set)
335 {
336         return afb_api_so_add_pathset(pathset, declare_set, call_set, 1);
337 }
338
339 int afb_api_so_add_pathset_nofails(const char *pathset, struct afb_apiset *declare_set, struct afb_apiset * call_set)
340 {
341         return afb_api_so_add_pathset(pathset, declare_set, call_set, 0);
342 }
343
344 #endif