├── Makefile ├── README.md ├── libunionpreload.c └── test-libunionpreload.sh /Makefile: -------------------------------------------------------------------------------- 1 | a: 2 | gcc -shared -fPIC libunionpreload.c -o libunionpreload.so -ldl -DUNION_LIBNAME=\"libunionpreload.so\" 3 | strip libunionpreload.so -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libunionpreload | [Download amd64](https://github.com/project-portable/libunionpreload/releases/download/amd64/libunionpreload.so) 2 | This lib tries to create the impression of a union filesystem, effectively creating the impression to the payload application that the specified directory are overlayed over /. This works as long as the payload application is a dynamically linked binary. It also intercepts "execve" calls to ensure that the sub processes also believe that the current directory is overlayed. 3 | 4 | # How it works in praticle: 5 | 6 | ```bash 7 | export CURRENT_DIRECTORY="$(dirname "$(readlink -f "${0}")")" 8 | export UNION_PRELOAD="/example" 9 | export LD_PRELOAD="${CURRENT_DIRECTORY}/libunionpreload.so" 10 | 11 | # If The application try access /usr/lib/bar.so, libunionpreload.so will "replace" 12 | # the / directory with /example, so, application now is looking for /example/usr/lib/bar.so, 13 | # if the file exists it is will be used even if the original looking file (/usr/lib/bar.so) exist. 14 | # If /example/usr/lib/bar.so doesn't exists, the /usr/lib/bar.so is used 15 | ``` 16 | 17 | # How to use 18 | Since it works by reading an environment variable and needs to be preloaded, you will need a `bash script` with the following lines before running your application: 19 | 20 | ```bash 21 | #!/usr/bin/env sh 22 | 23 | export CURRENT_DIRECTORY="$(dirname "$(readlink -f "${0}")")" 24 | export UNION_PRELOAD="${CURRENT_DIRECTORY}" 25 | export LD_PRELOAD="${CURRENT_DIRECTORY}/libunionpreload.so" 26 | 27 | # call your application here 28 | ``` 29 | 30 | # How to build 31 | 1. Clone repository: 32 | 33 | ``` 34 | git clone https://github.com/project-portable/libunionpreload.git 35 | ``` 36 | 2. Enter repository 37 | ``` 38 | cd libunionpreload 39 | ``` 40 | 3. Run `make` 41 | ``` 42 | make 43 | ``` 44 | # See in action 45 | 46 | Run test script: 47 | 48 | ``` 49 | sh ./test-libunionpreload.sh 50 | ``` 51 | # Credits 52 | 53 | * [Michael Terry](https://github.com/mikix/deb2snap/blob/master/src/preload.c) 54 | -------------------------------------------------------------------------------- /libunionpreload.c: -------------------------------------------------------------------------------- 1 | /* -*- Mode: C; indent-tabs-mode: nil; tab-width: 4 -*- 2 | * 3 | * Copyright (C) 2015 Canonical, Ltd. 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; version 3. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #define _GNU_SOURCE 19 | #define __USE_GNU 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #define LD_PRELOAD "LD_PRELOAD" 37 | #define UNION_PRELOAD "UNION_PRELOAD" 38 | static char **saved_ld_preloads = NULL; 39 | static size_t num_saved_ld_preloads = 0; 40 | static char *saved_union_preload = NULL; 41 | static char *saved_tmpdir = NULL; 42 | static char *saved_varlib = NULL; 43 | 44 | static void constructor() __attribute__((constructor)); 45 | 46 | static char * 47 | getenvdup (const char *varname) 48 | { 49 | char *envvar = secure_getenv (varname); 50 | if (envvar == NULL || envvar[0] == 0) // identical for our purposes 51 | return NULL; 52 | else 53 | return strdup (envvar); 54 | } 55 | 56 | void constructor() 57 | { 58 | char *ld_preload_copy, *p, *savedptr = NULL; 59 | size_t libnamelen; 60 | 61 | // We need to save LD_PRELOAD and UNION_PRELOAD in case we need to 62 | // propagate the values to an exec'd program. 63 | ld_preload_copy = getenvdup (LD_PRELOAD); 64 | if (ld_preload_copy == NULL) { 65 | return; 66 | } 67 | 68 | saved_union_preload = getenvdup (UNION_PRELOAD); 69 | if (saved_union_preload == NULL) { 70 | free (ld_preload_copy); 71 | return; 72 | } 73 | 74 | saved_tmpdir = getenvdup ("UNION_APP_TMPDIR"); 75 | if (!saved_tmpdir) { 76 | saved_tmpdir = getenvdup ("TMPDIR"); 77 | } 78 | 79 | saved_varlib = getenvdup ("UNION_APP_DATA_PATH"); 80 | if (!saved_varlib) { 81 | saved_varlib = getenvdup ("UNION_APP_DATA_PATH"); 82 | } 83 | 84 | // Pull out each absolute-pathed libunionpreload.so we find. Better to 85 | // accidentally include some other libunionpreload than not propagate 86 | // ourselves. 87 | libnamelen = strlen (UNION_LIBNAME); 88 | for (p = strtok_r (ld_preload_copy, " :", &savedptr); 89 | p; 90 | p = strtok_r (NULL, " :", &savedptr)) { 91 | size_t plen = strlen (p); 92 | if (plen > libnamelen && p[0] == '/' && strcmp (p + strlen (p) - strlen (UNION_LIBNAME) - 1, "/" UNION_LIBNAME) == 0) { 93 | num_saved_ld_preloads++; 94 | saved_ld_preloads = realloc (saved_ld_preloads, (num_saved_ld_preloads + 1) * sizeof (char *)); 95 | saved_ld_preloads[num_saved_ld_preloads - 1] = strdup (p); 96 | saved_ld_preloads[num_saved_ld_preloads] = NULL; 97 | } 98 | } 99 | free (ld_preload_copy); 100 | } 101 | 102 | static char * 103 | redirect_writable_path (const char *pathname, const char *basepath) 104 | { 105 | char *redirected_pathname; 106 | int chop = 0; 107 | 108 | if (pathname[0] == 0) { 109 | return strdup (basepath); 110 | } 111 | 112 | redirected_pathname = malloc (PATH_MAX); 113 | 114 | if (basepath[strlen (basepath) - 1] == '/') { 115 | chop = 1; 116 | } 117 | strncpy (redirected_pathname, basepath, PATH_MAX - 1 - chop); 118 | 119 | strncat (redirected_pathname, pathname, PATH_MAX - 1 - strlen (redirected_pathname)); 120 | 121 | // No need to see if it already exists -- app can only be in TMPDIR, not /tmp 122 | return redirected_pathname; 123 | } 124 | 125 | static char * 126 | redirect_path_full (const char *pathname, int check_parent, int only_if_absolute) 127 | { 128 | int (*_access) (const char *pathname, int mode); 129 | char *redirected_pathname; 130 | char *preload_dir; 131 | int ret; 132 | int chop = 0; 133 | char *slash = 0; 134 | 135 | if (pathname == NULL) { 136 | return NULL; 137 | } 138 | 139 | preload_dir = saved_union_preload; 140 | if (preload_dir == NULL) { 141 | return strdup (pathname); 142 | } 143 | 144 | // Do not redirect when accessing /dev 145 | if (strcmp (pathname, "/dev") == 0 || strncmp (pathname, "/dev/", 5) == 0) { 146 | return strdup (pathname); 147 | } 148 | 149 | if (only_if_absolute && pathname[0] != '/') { 150 | return strdup (pathname); 151 | } 152 | 153 | // Sometimes programs will hardcode /tmp (like Xorg does for its lock file). 154 | // In that case, let's redirect to TMPDIR. 155 | if (strcmp (pathname, "/tmp") == 0 || strncmp (pathname, "/tmp/", 5) == 0) { 156 | if (saved_tmpdir && strncmp (pathname, saved_tmpdir, strlen (saved_tmpdir)) != 0) { 157 | return redirect_writable_path (pathname + 4, saved_tmpdir); 158 | } else { 159 | return strdup (pathname); 160 | } 161 | } 162 | 163 | _access = (int (*)(const char *pathname, int mode)) dlsym (RTLD_NEXT, "access"); 164 | 165 | // And each app should have its own /var/lib writable tree. Here, we want 166 | // to support reading the base system's files if they exist, else let the app 167 | // play in /var/lib themselves. So we reverse the normal check: first see if 168 | // it exists in root, else do our redirection. 169 | if (strcmp (pathname, "/var/lib") == 0 || strncmp (pathname, "/var/lib/", 9) == 0) { 170 | if (saved_varlib && strncmp (pathname, saved_varlib, strlen (saved_varlib)) != 0 && _access (pathname, F_OK) != 0) { 171 | return redirect_writable_path (pathname + 8, saved_varlib); 172 | } else { 173 | return strdup (pathname); 174 | } 175 | } 176 | 177 | redirected_pathname = malloc (PATH_MAX); 178 | 179 | if (preload_dir[strlen (preload_dir) - 1] == '/') { 180 | chop = 1; 181 | } 182 | strncpy (redirected_pathname, preload_dir, PATH_MAX - 1 - chop); 183 | 184 | if (pathname[0] != '/') { 185 | size_t cursize = strlen (redirected_pathname); 186 | if (getcwd (redirected_pathname + cursize, PATH_MAX - cursize) == NULL) { 187 | free (redirected_pathname); 188 | return strdup (pathname); 189 | } 190 | strncat (redirected_pathname, "/", PATH_MAX - 1 - strlen (redirected_pathname)); 191 | } 192 | 193 | strncat (redirected_pathname, pathname, PATH_MAX - 1 - strlen (redirected_pathname)); 194 | 195 | if (check_parent) { 196 | slash = strrchr (redirected_pathname, '/'); 197 | if (slash) { // should always be true 198 | *slash = 0; 199 | } 200 | } 201 | 202 | ret = _access (redirected_pathname, F_OK); 203 | 204 | if (check_parent && slash) { 205 | *slash = '/'; 206 | } 207 | 208 | if (ret == 0 || errno == ENOTDIR) { // ENOTDIR is OK because it exists at least 209 | return redirected_pathname; 210 | } else { 211 | free (redirected_pathname); 212 | return strdup (pathname); 213 | } 214 | } 215 | 216 | static char * 217 | redirect_path (const char *pathname) 218 | { 219 | return redirect_path_full (pathname, 0, 0); 220 | } 221 | 222 | static char * 223 | redirect_path_target (const char *pathname) 224 | { 225 | return redirect_path_full (pathname, 1, 0); 226 | } 227 | 228 | static char * 229 | redirect_path_if_absolute (const char *pathname) 230 | { 231 | return redirect_path_full (pathname, 0, 1); 232 | } 233 | 234 | #define REDIRECT_1_1(RET, NAME) \ 235 | RET \ 236 | NAME (const char *path) \ 237 | { \ 238 | RET (*_NAME) (const char *path); \ 239 | char *new_path = NULL; \ 240 | RET result; \ 241 | _NAME = (RET (*)(const char *path)) dlsym (RTLD_NEXT, #NAME); \ 242 | new_path = redirect_path (path); \ 243 | result = _NAME (new_path); \ 244 | free (new_path); \ 245 | return result; \ 246 | } 247 | 248 | #define REDIRECT_1_2(RET, NAME, T2) \ 249 | RET \ 250 | NAME (const char *path, T2 A2) \ 251 | { \ 252 | RET (*_NAME) (const char *path, T2 A2); \ 253 | char *new_path = NULL; \ 254 | RET result; \ 255 | _NAME = (RET (*)(const char *path, T2 A2)) dlsym (RTLD_NEXT, #NAME); \ 256 | new_path = redirect_path (path); \ 257 | result = _NAME (new_path, A2); \ 258 | free (new_path); \ 259 | return result; \ 260 | } 261 | 262 | #define REDIRECT_1_3(RET, NAME, T2, T3) \ 263 | RET \ 264 | NAME (const char *path, T2 A2, T3 A3) \ 265 | { \ 266 | RET (*_NAME) (const char *path, T2 A2, T3 A3); \ 267 | char *new_path = NULL; \ 268 | RET result; \ 269 | _NAME = (RET (*)(const char *path, T2 A2, T3 A3)) dlsym (RTLD_NEXT, #NAME); \ 270 | new_path = redirect_path (path); \ 271 | result = _NAME (new_path, A2, A3); \ 272 | free (new_path); \ 273 | return result; \ 274 | } 275 | 276 | #define REDIRECT_2_2(RET, NAME, T1) \ 277 | RET \ 278 | NAME (T1 A1, const char *path) \ 279 | { \ 280 | RET (*_NAME) (T1 A1, const char *path); \ 281 | char *new_path = NULL; \ 282 | RET result; \ 283 | _NAME = (RET (*)(T1 A1, const char *path)) dlsym (RTLD_NEXT, #NAME); \ 284 | new_path = redirect_path (path); \ 285 | result = _NAME (A1, new_path); \ 286 | free (new_path); \ 287 | return result; \ 288 | } 289 | 290 | #define REDIRECT_2_3(RET, NAME, T1, T3) \ 291 | RET \ 292 | NAME (T1 A1, const char *path, T3 A3) \ 293 | { \ 294 | RET (*_NAME) (T1 A1, const char *path, T3 A3); \ 295 | char *new_path = NULL; \ 296 | RET result; \ 297 | _NAME = (RET (*)(T1 A1, const char *path, T3 A3)) dlsym (RTLD_NEXT, #NAME); \ 298 | new_path = redirect_path (path); \ 299 | result = _NAME (A1, new_path, A3); \ 300 | free (new_path); \ 301 | return result; \ 302 | } 303 | 304 | #define REDIRECT_2_3_AT(RET, NAME, T1, T3) \ 305 | RET \ 306 | NAME (T1 A1, const char *path, T3 A3) \ 307 | { \ 308 | RET (*_NAME) (T1 A1, const char *path, T3 A3); \ 309 | char *new_path = NULL; \ 310 | RET result; \ 311 | _NAME = (RET (*)(T1 A1, const char *path, T3 A3)) dlsym (RTLD_NEXT, #NAME); \ 312 | new_path = redirect_path_if_absolute (path); \ 313 | result = _NAME (A1, new_path, A3); \ 314 | free (new_path); \ 315 | return result; \ 316 | } 317 | 318 | #define REDIRECT_2_4_AT(RET, NAME, T1, T3, T4) \ 319 | RET \ 320 | NAME (T1 A1, const char *path, T3 A3, T4 A4) \ 321 | { \ 322 | RET (*_NAME) (T1 A1, const char *path, T3 A3, T4 A4); \ 323 | char *new_path = NULL; \ 324 | RET result; \ 325 | _NAME = (RET (*)(T1 A1, const char *path, T3 A3, T4 A4)) dlsym (RTLD_NEXT, #NAME); \ 326 | new_path = redirect_path_if_absolute (path); \ 327 | result = _NAME (A1, new_path, A3, A4); \ 328 | free (new_path); \ 329 | return result; \ 330 | } 331 | 332 | #define REDIRECT_3_5(RET, NAME, T1, T2, T4, T5) \ 333 | RET \ 334 | NAME (T1 A1, T2 A2, const char *path, T4 A4, T5 A5) \ 335 | { \ 336 | RET (*_NAME) (T1 A1, T2 A2, const char *path, T4 A4, T5 A5); \ 337 | char *new_path = NULL; \ 338 | RET result; \ 339 | _NAME = (RET (*)(T1 A1, T2 A2, const char *path, T4 A4, T5 A5)) dlsym (RTLD_NEXT, #NAME); \ 340 | new_path = redirect_path (path); \ 341 | result = _NAME (A1, A2, new_path, A4, A5); \ 342 | free (new_path); \ 343 | return result; \ 344 | } 345 | 346 | #define REDIRECT_TARGET(RET, NAME) \ 347 | RET \ 348 | NAME (const char *path, const char *target) \ 349 | { \ 350 | RET (*_NAME) (const char *path, const char *target); \ 351 | char *new_path = NULL; \ 352 | char *new_target = NULL; \ 353 | RET result; \ 354 | _NAME = (RET (*)(const char *path, const char *target)) dlsym (RTLD_NEXT, #NAME); \ 355 | new_path = redirect_path (path); \ 356 | new_target = redirect_path_target (target); \ 357 | result = _NAME (new_path, new_target); \ 358 | free (new_path); \ 359 | free (new_target); \ 360 | return result; \ 361 | } 362 | 363 | #define REDIRECT_OPEN(NAME) \ 364 | int \ 365 | NAME (const char *path, int flags, ...) \ 366 | { \ 367 | int mode = 0; \ 368 | int (*_NAME) (const char *path, int flags, mode_t mode); \ 369 | char *new_path = NULL; \ 370 | int result; \ 371 | if (flags & (O_CREAT|O_TMPFILE)) \ 372 | { \ 373 | va_list ap; \ 374 | va_start (ap, flags); \ 375 | mode = va_arg (ap, mode_t); \ 376 | va_end (ap); \ 377 | } \ 378 | _NAME = (int (*)(const char *path, int flags, mode_t mode)) dlsym (RTLD_NEXT, #NAME); \ 379 | new_path = redirect_path (path); \ 380 | result = _NAME (new_path, flags, mode); \ 381 | free (new_path); \ 382 | return result; \ 383 | } 384 | 385 | #define REDIRECT_OPEN_AT(NAME) \ 386 | int \ 387 | NAME (int dirfp, const char *path, int flags, ...) \ 388 | { \ 389 | int mode = 0; \ 390 | int (*_NAME) (int dirfp, const char *path, int flags, mode_t mode); \ 391 | char *new_path = NULL; \ 392 | int result; \ 393 | if (flags & (O_CREAT|O_TMPFILE)) \ 394 | { \ 395 | va_list ap; \ 396 | va_start (ap, flags); \ 397 | mode = va_arg (ap, mode_t); \ 398 | va_end (ap); \ 399 | } \ 400 | _NAME = (int (*)(int dirfp, const char *path, int flags, mode_t mode)) dlsym (RTLD_NEXT, #NAME); \ 401 | new_path = redirect_path_if_absolute (path); \ 402 | result = _NAME (dirfp, new_path, flags, mode); \ 403 | free (new_path); \ 404 | return result; \ 405 | } 406 | 407 | REDIRECT_1_2(FILE *, fopen, const char *) 408 | REDIRECT_1_2(FILE *, fopen64, const char *) 409 | REDIRECT_1_1(int, unlink) 410 | REDIRECT_2_3_AT(int, unlinkat, int, int) 411 | REDIRECT_1_2(int, access, int) 412 | REDIRECT_1_2(int, eaccess, int) 413 | REDIRECT_1_2(int, euidaccess, int) 414 | REDIRECT_2_4_AT(int, faccessat, int, int, int) 415 | REDIRECT_1_2(int, stat, struct stat *) 416 | REDIRECT_1_2(int, stat64, struct stat64 *) 417 | REDIRECT_1_2(int, lstat, struct stat *) 418 | REDIRECT_1_2(int, lstat64, struct stat64 *) 419 | REDIRECT_1_2(int, creat, mode_t) 420 | REDIRECT_1_2(int, creat64, mode_t) 421 | REDIRECT_1_2(int, truncate, off_t) 422 | REDIRECT_2_2(char *, bindtextdomain, const char *) 423 | REDIRECT_2_3(int, __xstat, int, struct stat *) 424 | REDIRECT_2_3(int, __xstat64, int, struct stat64 *) 425 | REDIRECT_2_3(int, __lxstat, int, struct stat *) 426 | REDIRECT_2_3(int, __lxstat64, int, struct stat64 *) 427 | REDIRECT_3_5(int, __fxstatat, int, int, struct stat *, int) 428 | REDIRECT_3_5(int, __fxstatat64, int, int, struct stat64 *, int) 429 | REDIRECT_1_2(int, statfs, struct statfs *) 430 | REDIRECT_1_2(int, statfs64, struct statfs64 *) 431 | REDIRECT_1_2(int, statvfs, struct statvfs *) 432 | REDIRECT_1_2(int, statvfs64, struct statvfs64 *) 433 | REDIRECT_1_2(long, pathconf, int) 434 | REDIRECT_1_1(DIR *, opendir) 435 | REDIRECT_1_2(int, mkdir, mode_t) 436 | REDIRECT_1_1(int, rmdir) 437 | REDIRECT_1_3(int, chown, uid_t, gid_t) 438 | REDIRECT_1_3(int, lchown, uid_t, gid_t) 439 | REDIRECT_1_2(int, chmod, mode_t) 440 | REDIRECT_1_2(int, lchmod, mode_t) 441 | REDIRECT_1_1(int, chdir) 442 | REDIRECT_1_3(ssize_t, readlink, char *, size_t) 443 | REDIRECT_1_2(char *, realpath, char *) 444 | REDIRECT_TARGET(int, link) 445 | REDIRECT_TARGET(int, rename) 446 | REDIRECT_OPEN(open) 447 | REDIRECT_OPEN(open64) 448 | REDIRECT_OPEN_AT(openat) 449 | REDIRECT_OPEN_AT(openat64) 450 | REDIRECT_2_3(int, inotify_add_watch, int, uint32_t) 451 | 452 | int 453 | scandir (const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **)) 454 | { 455 | int (*_scandir) (const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **)); 456 | char *new_path = NULL; 457 | int ret; 458 | 459 | _scandir = (int (*)(const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **))) dlsym (RTLD_NEXT, "scandir"); 460 | 461 | new_path = redirect_path (dirp); 462 | ret = _scandir (new_path, namelist, filter, compar); 463 | free (new_path); 464 | 465 | return ret; 466 | } 467 | 468 | int 469 | scandir64 (const char *dirp, struct dirent64 ***namelist, 470 | int (*filter)(const struct dirent64 *), 471 | int (*compar)(const struct dirent64 **, const struct dirent64 **)) 472 | { 473 | int (*_scandir64) (const char *dirp, struct dirent64 ***namelist, 474 | int (*filter)(const struct dirent64 *), 475 | int (*compar)(const struct dirent64 **, const struct dirent64 **)); 476 | char *new_path = NULL; 477 | int ret; 478 | 479 | _scandir64 = (int (*)(const char *dirp, struct dirent64 ***namelist, 480 | int (*filter)(const struct dirent64 *), 481 | int (*compar)(const struct dirent64 **, const struct dirent64 **))) 482 | dlsym (RTLD_NEXT, "scandir64"); 483 | 484 | new_path = redirect_path (dirp); 485 | ret = _scandir64 (new_path, namelist, filter, compar); 486 | free (new_path); 487 | 488 | return ret; 489 | } 490 | 491 | 492 | int 493 | scandirat (int dirfd, const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **)) 494 | { 495 | int (*_scandirat) (int dirfd, const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **)); 496 | char *new_path = NULL; 497 | int ret; 498 | 499 | _scandirat = (int (*)(int dirfd, const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **))) dlsym (RTLD_NEXT, "scandirat"); 500 | 501 | new_path = redirect_path_if_absolute (dirp); 502 | ret = _scandirat (dirfd, new_path, namelist, filter, compar); 503 | free (new_path); 504 | 505 | return ret; 506 | } 507 | 508 | int 509 | scandirat64 (int dirfd, const char *dirp, struct dirent64 ***namelist, 510 | int (*filter)(const struct dirent64 *), 511 | int (*compar)(const struct dirent64 **, const struct dirent64 **)) 512 | { 513 | int (*_scandirat64) (int dirfd, const char *dirp, struct dirent64 ***namelist, 514 | int (*filter)(const struct dirent64 *), 515 | int (*compar)(const struct dirent64 **, const struct dirent64 **)); 516 | char *new_path = NULL; 517 | int ret; 518 | 519 | _scandirat64 = (int (*)(int dirfd, const char *dirp, struct dirent64 ***namelist, 520 | int (*filter)(const struct dirent64 *), 521 | int (*compar)(const struct dirent64 **, const struct dirent64 **))) 522 | dlsym (RTLD_NEXT, "scandirat64"); 523 | 524 | new_path = redirect_path_if_absolute (dirp); 525 | ret = _scandirat64 (dirfd, new_path, namelist, filter, compar); 526 | free (new_path); 527 | 528 | return ret; 529 | } 530 | 531 | int 532 | bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen) 533 | { 534 | int (*_bind) (int sockfd, const struct sockaddr *addr, socklen_t addrlen); 535 | int result; 536 | 537 | _bind = (int (*)(int sockfd, const struct sockaddr *addr, socklen_t addrlen)) dlsym (RTLD_NEXT, "bind"); 538 | 539 | if (addr->sa_family == AF_UNIX && ((const struct sockaddr_un *)addr)->sun_path[0] != 0) { // could be abstract socket 540 | char *new_path = NULL; 541 | struct sockaddr_un new_addr; 542 | 543 | new_path = redirect_path (((const struct sockaddr_un *)addr)->sun_path); 544 | 545 | new_addr.sun_family = AF_UNIX; 546 | strcpy (new_addr.sun_path, new_path); 547 | free (new_path); 548 | 549 | result = _bind (sockfd, (const struct sockaddr *)&new_addr, sizeof(new_addr)); 550 | } else { 551 | result = _bind (sockfd, addr, addrlen); 552 | } 553 | 554 | return result; 555 | } 556 | 557 | int 558 | connect (int sockfd, const struct sockaddr *addr, socklen_t addrlen) 559 | { 560 | int (*_connect) (int sockfd, const struct sockaddr *addr, socklen_t addrlen); 561 | 562 | _connect = (int (*)(int sockfd, const struct sockaddr *addr, socklen_t addrlen)) dlsym (RTLD_NEXT, "connect"); 563 | 564 | /* addrlen == sizeof(sa_family_t) is the case of unnamed sockets, 565 | * and first byte of sun_path is 0 for abstract sockets. 566 | */ 567 | if (addr->sa_family == AF_UNIX 568 | && addrlen > sizeof(sa_family_t) 569 | && ((const struct sockaddr_un *) addr)->sun_path[0] != '\0') { 570 | 571 | const struct sockaddr_un *un_addr = (const struct sockaddr_un *) addr; 572 | char *new_path = NULL; 573 | struct sockaddr_un new_addr; 574 | 575 | new_path = redirect_path (un_addr->sun_path); 576 | 577 | new_addr.sun_family = AF_UNIX; 578 | strcpy (new_addr.sun_path, new_path); 579 | free (new_path); 580 | 581 | return _connect (sockfd, (const struct sockaddr *)&new_addr, sizeof(new_addr)); 582 | } 583 | 584 | return _connect (sockfd, addr, addrlen); 585 | } 586 | 587 | void * 588 | dlopen (const char *path, int mode) 589 | { 590 | void *(*_dlopen) (const char *path, int mode); 591 | char *new_path = NULL; 592 | void *result; 593 | 594 | _dlopen = (void *(*)(const char *path, int mode)) dlsym (RTLD_NEXT, "dlopen"); 595 | 596 | if (path && path[0] == '/') { 597 | new_path = redirect_path (path); 598 | result = _dlopen (new_path, mode); 599 | free (new_path); 600 | } else { 601 | // non-absolute library paths aren't simply relative paths, they need 602 | // a whole lookup algorithm 603 | result = _dlopen (path, mode); 604 | } 605 | 606 | return result; 607 | } 608 | 609 | static char * 610 | ensure_in_ld_preload (char *ld_preload, const char *to_be_added) 611 | { 612 | if (ld_preload && ld_preload[0] != 0) { 613 | char *ld_preload_copy; 614 | char *p, *savedptr = NULL; 615 | int found = 0; 616 | 617 | // Check if we are already in LD_PRELOAD and thus can bail 618 | ld_preload_copy = strdup (ld_preload); 619 | for (p = strtok_r (ld_preload_copy + strlen (LD_PRELOAD) + 1, " :", &savedptr); 620 | p; 621 | p = strtok_r (NULL, " :", &savedptr)) { 622 | if (strcmp (p, to_be_added) == 0) { 623 | found = 1; 624 | break; 625 | } 626 | } 627 | free (ld_preload_copy); 628 | 629 | if (!found) { 630 | ld_preload = realloc (ld_preload, strlen (to_be_added) + strlen (ld_preload) + 2); 631 | strcat (ld_preload, ":"); 632 | strcat (ld_preload, to_be_added); 633 | } 634 | } else { 635 | ld_preload = realloc (ld_preload, strlen (to_be_added) + strlen (LD_PRELOAD) + 2); 636 | strcpy (ld_preload, LD_PRELOAD "="); 637 | strcat (ld_preload, to_be_added); 638 | } 639 | 640 | return ld_preload; 641 | } 642 | 643 | static char ** 644 | execve_copy_envp (char *const envp[]) 645 | { 646 | int i, num_elements; 647 | char **new_envp = NULL; 648 | char *ld_preload = NULL; 649 | char *union_preload = NULL; 650 | 651 | for (num_elements = 0; envp && envp[num_elements]; num_elements++) { 652 | // this space intentionally left blank 653 | } 654 | 655 | new_envp = malloc (sizeof (char *) * (num_elements + 3)); 656 | 657 | for (i = 0; i < num_elements; i++) { 658 | new_envp[i] = strdup (envp[i]); 659 | if (strncmp (envp[i], LD_PRELOAD "=", strlen (LD_PRELOAD) + 1) == 0) { 660 | ld_preload = new_envp[i]; // point at last defined LD_PRELOAD 661 | } 662 | } 663 | 664 | if (saved_ld_preloads) { 665 | size_t j; 666 | char *ld_preload_copy; 667 | ld_preload_copy = ld_preload ? strdup (ld_preload) : NULL; 668 | for (j = 0; j < num_saved_ld_preloads; j++) { 669 | ld_preload_copy = ensure_in_ld_preload(ld_preload_copy, saved_ld_preloads[j]); 670 | } 671 | new_envp[i++] = ld_preload_copy; 672 | } 673 | 674 | if (saved_union_preload) { 675 | union_preload = malloc (strlen (saved_union_preload) + strlen (UNION_PRELOAD) + 2); 676 | strcpy (union_preload, UNION_PRELOAD "="); 677 | strcat (union_preload, saved_union_preload); 678 | new_envp[i++] = union_preload; 679 | } 680 | 681 | new_envp[i++] = NULL; 682 | return new_envp; 683 | } 684 | 685 | static int 686 | execve32_wrapper (int (*_execve) (const char *path, char *const argv[], char *const envp[]), char *path, char *const argv[], char *const envp[]) 687 | { 688 | char *custom_loader = NULL; 689 | char **new_argv; 690 | int i, num_elements, result; 691 | 692 | custom_loader = redirect_path ("/lib/ld-linux.so.2"); 693 | if (strcmp (custom_loader, "/lib/ld-linux.so.2") == 0) { 694 | free (custom_loader); 695 | return 0; 696 | } 697 | 698 | // envp is already adjusted for our needs. But we need to shift argv 699 | for (num_elements = 0; argv && argv[num_elements]; num_elements++) { 700 | // this space intentionally left blank 701 | } 702 | new_argv = malloc (sizeof (char *) * (num_elements + 2)); 703 | new_argv[0] = path; 704 | for (i = 0; i < num_elements; i++) { 705 | new_argv[i + 1] = argv[i]; 706 | } 707 | new_argv[num_elements + 1] = 0; 708 | 709 | // Now actually run execve with our loader and adjusted argv 710 | result = _execve (custom_loader, new_argv, envp); 711 | 712 | // Cleanup on error 713 | free (new_argv); 714 | free (custom_loader); 715 | return result; 716 | } 717 | 718 | static int 719 | execve_wrapper (const char *func, const char *path, char *const argv[], char *const envp[]) 720 | { 721 | int (*_execve) (const char *path, char *const argv[], char *const envp[]); 722 | char *new_path = NULL; 723 | char **new_envp = NULL; 724 | int i, result; 725 | 726 | _execve = (int (*)(const char *path, char *const argv[], char *const envp[])) dlsym (RTLD_NEXT, func); 727 | 728 | new_path = redirect_path (path); 729 | 730 | // Make sure we inject our original preload values, can't trust this 731 | // program to pass them along in envp for us. 732 | new_envp = execve_copy_envp (envp); 733 | 734 | result = _execve (new_path, argv, new_envp); 735 | 736 | if (result == -1 && errno == ENOENT) { 737 | // OK, get prepared for gross hacks here. In order to run 32-bit ELF 738 | // executables -- which will hardcode /lib/ld-linux.so.2 as their ld.so 739 | // loader, we must redirect that check to our own version of ld-linux.so.2. 740 | // But that lookup is done behind the scenes by execve, so we can't 741 | // intercept it like normal. Instead, we'll prefix the command by the 742 | // ld.so loader which will only work if the architecture matches. So if 743 | // we failed to run it normally above because the loader couldn't find 744 | // something, try with our own 32-bit loader. 745 | int (*_access) (const char *pathname, int mode); 746 | _access = (int (*)(const char *pathname, int mode)) dlsym (RTLD_NEXT, "access"); 747 | if (_access (new_path, F_OK) == 0) { 748 | // Only actually try this if the path actually did exist. That 749 | // means the ENOENT must have been a missing linked library or the 750 | // wrong ld.so loader. Lets assume the latter and try to run as 751 | // a 32-bit executable. 752 | result = execve32_wrapper (_execve, new_path, argv, new_envp); 753 | } 754 | } 755 | 756 | free (new_path); 757 | for (i = 0; new_envp[i]; i++) { 758 | free (new_envp[i]); 759 | } 760 | free (new_envp); 761 | 762 | return result; 763 | } 764 | 765 | int 766 | execv (const char *path, char *const argv[]) 767 | { 768 | return execve (path, argv, environ); 769 | } 770 | 771 | int 772 | execve (const char *path, char *const argv[], char *const envp[]) 773 | { 774 | return execve_wrapper ("execve", path, argv, envp); 775 | } 776 | 777 | int 778 | __execve (const char *path, char *const argv[], char *const envp[]) 779 | { 780 | return execve_wrapper ("__execve", path, argv, envp); 781 | } 782 | -------------------------------------------------------------------------------- /test-libunionpreload.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "\nContent of / before libunionpreload" 4 | echo "-----------------------------------\n" 5 | 6 | ls / 7 | 8 | export CURRENT_DIRECTORY="$(dirname "$(readlink -f "${0}")")" 9 | export UNION_PRELOAD="${CURRENT_DIRECTORY}" 10 | export LD_PRELOAD="${CURRENT_DIRECTORY}/libunionpreload.so" 11 | 12 | echo "\nContent of / after libunionpreload" 13 | echo "-----------------------------------\n" 14 | 15 | ls / 16 | 17 | echo 18 | --------------------------------------------------------------------------------