1 /*
2  * SPDX-License-Identifier: ISC
3  *
4  * Copyright (c) 2010, 2012-2014 Todd C. Miller <Todd.Miller@sudo.ws>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /*
20  * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21  * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22  */
23 
24 #include <config.h>
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #if defined(HAVE_SHL_LOAD)
30 # include <dl.h>
31 #elif defined(HAVE_DLOPEN)
32 # include <dlfcn.h>
33 #endif
34 #include <errno.h>
35 
36 #include "sudo_compat.h"
37 #include "sudo_dso.h"
38 
39 /*
40  * Pointer for statically compiled symbols.
41  */
42 static struct sudo_preload_table *preload_table;
43 
44 void
sudo_dso_preload_table_v1(struct sudo_preload_table * table)45 sudo_dso_preload_table_v1(struct sudo_preload_table *table)
46 {
47     preload_table = table;
48 }
49 
50 #if defined(HAVE_SHL_LOAD)
51 
52 # ifndef DYNAMIC_PATH
53 #  define DYNAMIC_PATH	0
54 # endif
55 
56 void *
sudo_dso_load_v1(const char * path,int mode)57 sudo_dso_load_v1(const char *path, int mode)
58 {
59     struct sudo_preload_table *pt;
60     int flags = DYNAMIC_PATH | BIND_VERBOSE;
61 
62     if (mode == 0)
63 	mode = SUDO_DSO_LAZY;	/* default behavior */
64 
65     /* Check prelinked symbols first. */
66     if (preload_table != NULL) {
67 	for (pt = preload_table; pt->handle != NULL; pt++) {
68 	    if (pt->path != NULL && strcmp(path, pt->path) == 0)
69 		return pt->handle;
70 	}
71     }
72 
73     /* We don't support SUDO_DSO_GLOBAL or SUDO_DSO_LOCAL yet. */
74     if (ISSET(mode, SUDO_DSO_LAZY))
75 	flags |= BIND_DEFERRED;
76     if (ISSET(mode, SUDO_DSO_NOW))
77 	flags |= BIND_IMMEDIATE;
78 
79     return (void *)shl_load(path, flags, 0L);
80 }
81 
82 int
sudo_dso_unload_v1(void * handle)83 sudo_dso_unload_v1(void *handle)
84 {
85     struct sudo_preload_table *pt;
86 
87     /* Check prelinked symbols first. */
88     if (preload_table != NULL) {
89 	for (pt = preload_table; pt->handle != NULL; pt++) {
90 	    if (pt->handle == handle)
91 		return 0;
92 	}
93     }
94 
95     return shl_unload((shl_t)handle);
96 }
97 
98 void *
sudo_dso_findsym_v1(void * vhandle,const char * symbol)99 sudo_dso_findsym_v1(void *vhandle, const char *symbol)
100 {
101     struct sudo_preload_table *pt;
102     shl_t handle = vhandle;
103     void *value = NULL;
104 
105     /* Check prelinked symbols first. */
106     if (preload_table != NULL) {
107 	for (pt = preload_table; pt->handle != NULL; pt++) {
108 	    if (pt->handle == handle) {
109 		struct sudo_preload_symbol *sym;
110 		for (sym = pt->symbols; sym->name != NULL; sym++) {
111 		    if (strcmp(sym->name, symbol) == 0)
112 			return sym->addr;
113 		}
114 		errno = ENOENT;
115 		return NULL;
116 	    }
117 	}
118     }
119 
120     /*
121      * Note that the behavior of of SUDO_DSO_NEXT and SUDO_DSO_SELF
122      * differs from most implementations when called from
123      * a shared library.
124      */
125     if (vhandle == SUDO_DSO_NEXT) {
126 	/* Iterate over all shared libs looking for symbol. */
127 	shl_t myhandle = PROG_HANDLE;
128 	struct shl_descriptor *desc;
129 	int idx = 0;
130 
131 	/* Find program's real handle. */
132 	if (shl_gethandle(PROG_HANDLE, &desc) == 0)
133 	    myhandle = desc->handle;
134 	while (shl_get(idx++, &desc) == 0) {
135 	    if (desc->handle == myhandle)
136 		continue;
137 	    if (shl_findsym(&desc->handle, symbol, TYPE_UNDEFINED, &value) == 0)
138 		break;
139 	}
140     } else {
141 	if (vhandle == SUDO_DSO_DEFAULT)
142 	    handle = NULL;
143 	else if (vhandle == SUDO_DSO_SELF)
144 	    handle = PROG_HANDLE;
145 	(void)shl_findsym(&handle, symbol, TYPE_UNDEFINED, &value);
146     }
147 
148     return value;
149 }
150 
151 char *
sudo_dso_strerror_v1(void)152 sudo_dso_strerror_v1(void)
153 {
154     return strerror(errno);
155 }
156 
157 #elif defined(HAVE_DLOPEN)
158 
159 # ifndef RTLD_GLOBAL
160 #  define RTLD_GLOBAL	0
161 # endif
162 
163 /* Default member names for AIX when dlopen()ing an ar (.a) file. */
164 # ifdef RTLD_MEMBER
165 #  ifdef __LP64__
166 #   define SUDO_DSO_MEMBER	"shr_64.o"
167 #  else
168 #   define SUDO_DSO_MEMBER	"shr.o"
169 #  endif
170 # endif
171 
172 void *
sudo_dso_load_v1(const char * path,int mode)173 sudo_dso_load_v1(const char *path, int mode)
174 {
175     struct sudo_preload_table *pt;
176     int flags = 0;
177     void *ret;
178 #ifdef RTLD_MEMBER
179     char *cp;
180 #endif
181 
182     /* Check prelinked symbols first. */
183     if (preload_table != NULL) {
184 	for (pt = preload_table; pt->handle != NULL; pt++) {
185 	    if (pt->path != NULL && strcmp(path, pt->path) == 0)
186 		return pt->handle;
187 	}
188     }
189 
190     /* Map SUDO_DSO_* -> RTLD_* */
191     if (ISSET(mode, SUDO_DSO_LAZY))
192 	SET(flags, RTLD_LAZY);
193     if (ISSET(mode, SUDO_DSO_NOW))
194 	SET(flags, RTLD_NOW);
195     if (ISSET(mode, SUDO_DSO_GLOBAL))
196 	SET(flags, RTLD_GLOBAL);
197     if (ISSET(mode, SUDO_DSO_LOCAL))
198 	SET(flags, RTLD_LOCAL);
199 
200 #ifdef RTLD_MEMBER
201     /* Check for AIX path(module) syntax and add RTLD_MEMBER for a module. */
202     cp = strrchr(path, '(');
203     if (cp != NULL) {
204 	size_t len = strlen(cp);
205 	if (len > 2 && cp[len - 1] == '\0')
206 	    SET(flags, RTLD_MEMBER);
207     }
208 #endif /* RTLD_MEMBER */
209     ret = dlopen(path, flags);
210 #ifdef RTLD_MEMBER
211     /*
212      * If we try to dlopen() an AIX .a file without an explicit member
213      * it will fail with ENOEXEC.  Try again using the default member.
214      */
215     if (ret == NULL && !ISSET(flags, RTLD_MEMBER) && errno == ENOEXEC) {
216 	if (asprintf(&cp, "%s(%s)", path, SUDO_DSO_MEMBER) != -1) {
217 	    ret = dlopen(cp, flags|RTLD_MEMBER);
218 	    free(cp);
219 	}
220     }
221 #endif /* RTLD_MEMBER */
222 
223     return ret;
224 }
225 
226 int
sudo_dso_unload_v1(void * handle)227 sudo_dso_unload_v1(void *handle)
228 {
229     struct sudo_preload_table *pt;
230 
231     /* Check prelinked symbols first. */
232     if (preload_table != NULL) {
233 	for (pt = preload_table; pt->handle != NULL; pt++) {
234 	    if (pt->handle == handle)
235 		return 0;
236 	}
237     }
238 
239     return dlclose(handle);
240 }
241 
242 void *
sudo_dso_findsym_v1(void * handle,const char * symbol)243 sudo_dso_findsym_v1(void *handle, const char *symbol)
244 {
245     struct sudo_preload_table *pt;
246 
247     /* Check prelinked symbols first. */
248     if (preload_table != NULL) {
249 	for (pt = preload_table; pt->handle != NULL; pt++) {
250 	    if (pt->handle == handle) {
251 		struct sudo_preload_symbol *sym;
252 		for (sym = pt->symbols; sym->name != NULL; sym++) {
253 		    if (strcmp(sym->name, symbol) == 0)
254 			return sym->addr;
255 		}
256 		errno = ENOENT;
257 		return NULL;
258 	    }
259 	}
260     }
261 
262     /*
263      * Not all implementations support the special handles.
264      */
265     if (handle == SUDO_DSO_NEXT) {
266 # ifdef RTLD_NEXT
267 	handle = RTLD_NEXT;
268 # else
269 	errno = ENOENT;
270 	return NULL;
271 # endif
272     } else if (handle == SUDO_DSO_DEFAULT) {
273 # ifdef RTLD_DEFAULT
274 	handle = RTLD_DEFAULT;
275 # else
276 	errno = ENOENT;
277 	return NULL;
278 # endif
279     } else if (handle == SUDO_DSO_SELF) {
280 # ifdef RTLD_SELF
281 	handle = RTLD_SELF;
282 # else
283 	errno = ENOENT;
284 	return NULL;
285 # endif
286     }
287 
288     return dlsym(handle, symbol);
289 }
290 
291 char *
sudo_dso_strerror_v1(void)292 sudo_dso_strerror_v1(void)
293 {
294     return dlerror();
295 }
296 
297 #else /* !HAVE_SHL_LOAD && !HAVE_DLOPEN */
298 
299 /*
300  * Emulate dlopen() using a static list of symbols compiled into sudo.
301  */
302 void *
sudo_dso_load_v1(const char * path,int mode)303 sudo_dso_load_v1(const char *path, int mode)
304 {
305     struct sudo_preload_table *pt;
306 
307     /* Check prelinked symbols first. */
308     if (preload_table != NULL) {
309 	for (pt = preload_table; pt->handle != NULL; pt++) {
310 	    if (pt->path != NULL && strcmp(path, pt->path) == 0)
311 		return pt->handle;
312 	}
313     }
314     return NULL;
315 }
316 
317 int
sudo_dso_unload_v1(void * handle)318 sudo_dso_unload_v1(void *handle)
319 {
320     struct sudo_preload_table *pt;
321 
322     if (preload_table != NULL) {
323 	for (pt = preload_table; pt->handle != NULL; pt++) {
324 	    if (pt->handle == handle)
325 		return 0;
326 	}
327     }
328     return -1;
329 }
330 
331 void *
sudo_dso_findsym_v1(void * handle,const char * symbol)332 sudo_dso_findsym_v1(void *handle, const char *symbol)
333 {
334     struct sudo_preload_table *pt;
335 
336     if (preload_table != NULL) {
337 	for (pt = preload_table; pt->handle != NULL; pt++) {
338 	    if (pt->handle == handle) {
339 		struct sudo_preload_symbol *sym;
340 		for (sym = pt->symbols; sym->name != NULL; sym++) {
341 		    if (strcmp(sym->name, symbol) == 0)
342 			return sym->addr;
343 		}
344 	    }
345 	}
346     }
347     errno = ENOENT;
348     return NULL;
349 }
350 
351 char *
sudo_dso_strerror_v1(void)352 sudo_dso_strerror_v1(void)
353 {
354     return strerror(errno);
355 }
356 #endif /* !HAVE_SHL_LOAD && !HAVE_DLOPEN */
357