blob: 04de0bc483183b2d5e1de63cb3a96b157f5e3bde [file] [log] [blame]
/*
* Copyright (C) 2022 Igalia S.L.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <dirent.h>
#include <dlfcn.h>
#include <errno.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define dlopen_wrapper_print_error_msg(fmt, ...) \
do { \
if (dlopen_wrapper_debug_print >= 1) \
fprintf(stderr, "[pid=%d] ERROR at dlopenwrap.so: " fmt, getpid(), __VA_ARGS__); \
} while (0)
#define dlopen_wrapper_print_debug_msg(fmt, ...) \
do { \
if (dlopen_wrapper_debug_print >= 2) \
fprintf(stderr, "[pid=%d] dlopenwrap.so: " fmt, getpid(), __VA_ARGS__); \
} while (0)
static int dlopen_wrapper_debug_print = -1;
static int dlopen_wrapper_call_real_dlerror = 1;
static char* (*__real_dlerror)(void) = NULL;
// Returns the string "dirname/filename" into the buffer path_joint_dest
void dlopen_wrapper_path_join(const char* dirname, const char* filename, char* path_joint_dest)
{
path_joint_dest = strncpy(path_joint_dest, dirname, PATH_MAX);
// add a slash if needed
if (*(path_joint_dest + strlen(dirname) - 1) != '/') {
*(path_joint_dest + strlen(dirname)) = '/';
*(path_joint_dest + strlen(dirname) + 1) = 0;
}
strncat(path_joint_dest, filename, PATH_MAX);
}
int is_dir(unsigned char d_type, const char* filepath)
{
struct stat statbuf;
// we consider a symbolic link to a dir as a dir, also handle unknown.
if (d_type == DT_UNKNOWN || d_type == DT_LNK) {
if (stat(filepath, &statbuf))
dlopen_wrapper_print_error_msg("Calling stat on \"%s\": %s\n", filepath, strerror(errno));
return S_ISDIR(statbuf.st_mode);
}
return (d_type == DT_DIR);
}
// Recursive function to traverse a directory and obtain the path of the first file named "filename"
// It writes that path to the buffer "candidate_path_dest" if the file is found (return == 1).
// If the file is not found it returns 0 and the contents of "candidate_path_dest" are undefined.
int dlopen_wrapper_recursive_find(const char* filename_to_find, const char* directory_to_search, char* candidate_path_dest)
{
DIR *dp;
struct dirent* entry;
char subdirectory_to_search[PATH_MAX];
dp = opendir(directory_to_search);
if (!dp) {
dlopen_wrapper_print_error_msg("Opening directory \"%s\": %s\n", directory_to_search, strerror(errno));
return 0;
}
while (1) {
errno = 0;
entry = readdir(dp);
if (!entry) {
if (errno)
dlopen_wrapper_print_error_msg("Reading directory \"%s\": %s\n", directory_to_search, strerror(errno));
break;
}
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
continue;
dlopen_wrapper_path_join(directory_to_search, entry->d_name, candidate_path_dest);
if (is_dir(entry->d_type, candidate_path_dest)) {
dlopen_wrapper_path_join(directory_to_search, entry->d_name, subdirectory_to_search);
if (dlopen_wrapper_recursive_find(filename_to_find, subdirectory_to_search, candidate_path_dest)) {
closedir(dp);
return 1;
}
} else {
if (!strcmp(filename_to_find, entry->d_name)) {
if (!access(candidate_path_dest, F_OK | R_OK)) {
closedir(dp);
return 1;
}
dlopen_wrapper_print_error_msg("Access denied to candidate library \"%s\"\n", candidate_path_dest);
}
}
}
closedir(dp);
return 0;
}
// Helper function that checks if filename_original_path is a path and is inside libdir.
// If it can't find the file directly by the path, it calls dlopen_wrapper_recursive_find() to search it.
// It writes the path to the buffer "path_found_dest" if the file is found (return == 1).
// If the file is not found it returns 0 and the contents of "path_found_dest" are undefined.
int dlopen_wrapper_get_path_for_file_under_dir(const char* filename_original_path, const char* libdir, char* path_found_dest)
{
// basename() may modify the buffer passed on the first argument
char *original_path = strdup(filename_original_path);
char *base_file_to_dlopen_wrapper_find = basename(original_path);
int retcode = 1;
int filename_is_absolute = (*filename_original_path == '/');
int filename_is_relative = 0;
if (!filename_is_absolute) {
for (char* current_char = (char*)filename_original_path; *current_char; ++current_char) {
if (*current_char == '/') {
filename_is_relative = 1;
break;
}
}
}
if (filename_is_absolute) {
if (!strncmp(filename_original_path, libdir, strlen(libdir))) {
if (!access(filename_original_path, F_OK | R_OK)) {
dlopen_wrapper_print_debug_msg("Filename \"%s\" found under directory \"%s\" without any path rewriting\n", filename_original_path, libdir);
strncpy(path_found_dest, filename_original_path, PATH_MAX);
return 1;
}
}
dlopen_wrapper_print_debug_msg("Filename \"%s\" NOT found under directory \"%s\". Will try a search with basename\n", filename_original_path, libdir);
} else if (filename_is_relative) {
dlopen_wrapper_path_join(libdir, filename_original_path, path_found_dest);
if (!access(path_found_dest, F_OK | R_OK)) {
dlopen_wrapper_print_debug_msg("Filename \"%s\" converted to \"%s\" and found under directory \"%s\"\n", filename_original_path, path_found_dest, libdir);
return 1;
}
dlopen_wrapper_print_debug_msg("Filename \"%s\" NOT found under directory \"%s\"\n", filename_original_path, libdir);
}
dlopen_wrapper_print_debug_msg("Trying a search for basename \"%s\" under directory \"%s\"\n", base_file_to_dlopen_wrapper_find, libdir);
retcode = dlopen_wrapper_recursive_find(base_file_to_dlopen_wrapper_find, libdir, path_found_dest);
if (original_path)
free(original_path);
return retcode;
}
// Helper function to split the paths of ld_library_path and to search for "filename_original_path" inside those paths.
// It will stop searching at the first match.
// It writes the path to the buffer "path_found_dest" if the file is found (return == 1).
// If the file is not found it returns 0 and the contents of "path_found_dest" are undefined.
int dlopen_wrapper_find_library_on_libpath(const char* filename_original_path, const char* ld_library_path, char* path_found_dest)
{
char *saveptr = NULL;
char *library_paths = strdup(ld_library_path);
int found = 0;
for (char *libdir = strtok_r(library_paths, ":", &saveptr); libdir; libdir = strtok_r(NULL, ":", &saveptr)) {
// make the path absolute
char *libdir_rp = realpath(libdir, NULL);
if (!libdir_rp) {
dlopen_wrapper_print_error_msg("Calling realpath() on \"%s\": %s\n", libdir, strerror(errno));
continue;
}
dlopen_wrapper_print_debug_msg("Trying search under directory %s\n", libdir);
if (dlopen_wrapper_get_path_for_file_under_dir(filename_original_path, libdir_rp, path_found_dest)) {
free(libdir_rp);
found = 1;
break;
}
free(libdir_rp);
}
free(library_paths);
return found;
}
void *dlopen(const char* filename, int flags)
{
char new_file_path[PATH_MAX];
dlopen_wrapper_call_real_dlerror = 1;
if (dlopen_wrapper_debug_print == -1) {
char *dlopen_debug_value = getenv("DLOPEN_WRAP_DEBUG");
if (dlopen_debug_value)
dlopen_wrapper_debug_print = atoi(dlopen_debug_value);
}
static void* (*__real_dlopen)(const char* file, int mode) = NULL;
if (!__real_dlopen) {
__real_dlopen = dlsym(RTLD_NEXT, "dlopen");
if (!__real_dlopen) {
dlopen_wrapper_print_error_msg("Getting dlopen address: %s\n", dlerror());
dlopen_wrapper_call_real_dlerror = 0;
return NULL;
}
}
if (!__real_dlerror) {
__real_dlerror = dlsym(RTLD_NEXT, "dlerror");
if (!__real_dlerror) {
dlopen_wrapper_print_error_msg("Getting dlerror address: %s\n", dlerror());
return NULL;
}
}
// Don't do anything if filename is NULL or LD_LIBRARY_PATH not defined
if (!filename)
return __real_dlopen(filename, flags);
char *ld_library_path = getenv("LD_LIBRARY_PATH");
if (!ld_library_path)
return __real_dlopen(filename, flags);
if (!dlopen_wrapper_find_library_on_libpath(filename, ld_library_path, new_file_path)) {
dlopen_wrapper_print_error_msg("Could not find \"%s\" under the directori(es) of LD_LIBRARY_PATH. Refusing to dlopen it\n", filename);
dlopen_wrapper_call_real_dlerror = 0;
return NULL;
}
if (dlopen_wrapper_debug_print) {
if (strcmp(filename, new_file_path))
dlopen_wrapper_print_debug_msg("Original request to dlopen \"%s\" changed to load \"%s\"\n", filename, new_file_path);
else
dlopen_wrapper_print_debug_msg("Original request to dlopen \"%s\" allowed (unchanged) because is under the directori(es) of LD_LIBRARY_PATH\n", filename);
}
return __real_dlopen(new_file_path, flags);
}
void *dlmopen(Lmid_t lmid, const char* filename, int flags)
{
char new_file_path[PATH_MAX];
dlopen_wrapper_call_real_dlerror = 1;
if (dlopen_wrapper_debug_print == -1) {
char *dlopen_debug_value = getenv("DLOPEN_WRAP_DEBUG");
if (dlopen_debug_value)
dlopen_wrapper_debug_print = atoi(dlopen_debug_value);
}
static void* (*__real_dlmopen)(Lmid_t lmidt, const char* file, int mode) = NULL;
if (!__real_dlmopen) {
__real_dlmopen = dlsym(RTLD_NEXT, "dlmopen");
if (!__real_dlmopen) {
dlopen_wrapper_print_error_msg("Getting dlmopen address: %s\n", dlerror());
dlopen_wrapper_call_real_dlerror = 0;
return NULL;
}
}
if (!__real_dlerror) {
__real_dlerror = dlsym(RTLD_NEXT, "dlerror");
if (!__real_dlerror) {
dlopen_wrapper_print_error_msg("Getting dlerror address: %s\n", dlerror());
return NULL;
}
}
// Don't do anything if filename is NULL or LD_LIBRARY_PATH not defined
if (!filename)
return __real_dlmopen(lmid, filename, flags);
char *ld_library_path = getenv("LD_LIBRARY_PATH");
if (!ld_library_path)
return __real_dlmopen(lmid, filename, flags);
if (!dlopen_wrapper_find_library_on_libpath(filename, ld_library_path, new_file_path)) {
dlopen_wrapper_print_error_msg("Could not find \"%s\" under the directori(es) of LD_LIBRARY_PATH. Refusing to dlmopen it\n", filename);
dlopen_wrapper_call_real_dlerror = 0;
return NULL;
}
if (dlopen_wrapper_debug_print) {
if (strcmp(filename, new_file_path))
dlopen_wrapper_print_debug_msg("Original request to dlmopen \"%s\" changed to load \"%s\"\n", filename, new_file_path);
else
dlopen_wrapper_print_debug_msg("Original request to dlmopen \"%s\" allowed (unchanged) because is under the directori(es) of LD_LIBRARY_PATH\n", filename);
}
return __real_dlmopen(lmid, new_file_path, flags);
}
char *dlerror(void)
{
static char* wrapper_error_msg = "Call blocked by dlopenwrapper library.";
// __real_dlerror handle was already obtained before on the dlopen() or dlmopen() wrappers.
// Obtaining it here is not possible because calling dlsym() overwrites the dlerror buffer.
if (dlopen_wrapper_call_real_dlerror && __real_dlerror)
return __real_dlerror();
dlopen_wrapper_call_real_dlerror = 1;
return wrapper_error_msg;
}