Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.py
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,7 @@ def downloadPackage(name, url, filename, sha256):

arch_smoke = command(
name="arch_smoke",
inputs=["$(S)/tst/glibc_test.c", "$(S)/tst/glibc_exception_test.cpp", "$(S)/tst/glibc_lazy_test.c", "$(S)/tst/glibc_shim_test.c", "$(S)/tst/glibc_ie_test.c", "$(S)/tst/glibc_interpose_test.c", "$(S)/tst/glibc_overridable_test.c", "$(S)/tst/glibc_caller_test.c", "$(S)/tst/glibc_versioned_test.c", "$(S)/tst/glibc_version_consumer_test.c", "$(S)/tst/glibc_ie_gd_test.c", "$(S)/tst/glibc_ie_ref_test.c", "$(S)/tst/glibc_big_tls_test.c", "$(S)/tst/run_smoke.py", *archives],
inputs=["$(S)/tst/glibc_test.c", "$(S)/tst/glibc_exception_test.cpp", "$(S)/tst/glibc_lazy_test.c", "$(S)/tst/glibc_shim_test.c", "$(S)/tst/glibc_ie_test.c", "$(S)/tst/glibc_interpose_test.c", "$(S)/tst/glibc_overridable_test.c", "$(S)/tst/glibc_caller_test.c", "$(S)/tst/glibc_versioned_test.c", "$(S)/tst/glibc_version_consumer_test.c", "$(S)/tst/glibc_ie_gd_test.c", "$(S)/tst/glibc_ie_ref_test.c", "$(S)/tst/glibc_big_tls_test.c", "$(S)/tst/glibc_runpath_host_test.c", "$(S)/tst/glibc_runpath_sibling_test.c", "$(S)/tst/run_smoke.py", *archives],
outputs=["$(B)/tst/arch-smoke.log"],
deps=[smoke, *downloads],
cmd=[
Expand All @@ -962,6 +962,8 @@ def downloadPackage(name, url, filename, sha256):
"DLFCN_GLIBC_IE_GD_TEST_SOURCE": "$(S)/tst/glibc_ie_gd_test.c",
"DLFCN_GLIBC_IE_REF_TEST_SOURCE": "$(S)/tst/glibc_ie_ref_test.c",
"DLFCN_GLIBC_BIG_TLS_TEST_SOURCE": "$(S)/tst/glibc_big_tls_test.c",
"DLFCN_GLIBC_RUNPATH_HOST_TEST_SOURCE": "$(S)/tst/glibc_runpath_host_test.c",
"DLFCN_GLIBC_RUNPATH_SIBLING_TEST_SOURCE": "$(S)/tst/glibc_runpath_sibling_test.c",
"DLFCN_GLIBC_TEST_SOURCE": "$(S)/tst/glibc_test.c",
"DLFCN_SMOKE": "$(B)/tst/smoke",
"DLFCN_SYSROOT_LIB": sysroot_lib,
Expand Down
12 changes: 10 additions & 2 deletions lib/dlfcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ extern "C" void* stub_dlsym(void* handle, const char* symbol) {
return nullptr;
}

extern "C" void* stub_dlopen(const char* filename, int mode) {
static void* dlopenImpl(const void* caller, const char* filename, int mode) {
clearLastError();

try {
Expand All @@ -290,7 +290,7 @@ extern "C" void* stub_dlopen(const char* filename, int mode) {

// The loader keeps one wrapper per image, so repeated dlopen of the
// same library returns the same handle.
return ElfImage::loadElf(filename, mode);
return caller ? ElfImage::loadElfFrom(caller, filename, mode) : ElfImage::loadElf(filename, mode);
} catch (const std::exception& error) {
setLastError(error.what());
return nullptr;
Expand All @@ -300,6 +300,14 @@ extern "C" void* stub_dlopen(const char* filename, int mode) {
}
}

extern "C" void* stub_dlopen(const char* filename, int mode) {
return dlopenImpl(nullptr, filename, mode);
}

extern "C" void* stub_dlopen_from(const void* caller, const char* filename, int mode) {
return dlopenImpl(caller, filename, mode);
}

extern "C" int stub_dlclose(void* handle) {
clearLastError();

Expand Down
4 changes: 4 additions & 0 deletions lib/dlfcn.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ extern "C" {

void* stub_dlsym(void* handle, const char* symbol);
void* stub_dlopen(const char* filename, int flags);
// The dlopen issued by loaded code: caller is the return address the
// calling image will resume at, and its DT_RPATH/DT_RUNPATH join the
// library search for names without a slash.
void* stub_dlopen_from(const void* caller, const char* filename, int flags);
int stub_dlclose(void* handle);
char* stub_dlerror(void);
// Startup wiring: the registry is unsynchronized, so finish every
Expand Down
54 changes: 44 additions & 10 deletions lib/elf_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ namespace {

static Loader& instance();

LinkMap* load(const std::string_view& requestedPath, int flags);
LinkMap* load(const std::string_view& requestedPath, int flags, LinkMap* dlopenCaller);
void runPendingInitializers();

void* lookup(LinkMap& image, std::string_view name, std::string_view version);
Expand All @@ -359,6 +359,7 @@ namespace {
void makeGlobal(LinkMap& image);

bool findAddress(const void* address, ElfAddress* res);
LinkMap* findImageByAddress(const void* address);
int iterateProgramHeaders(ElfProgramHeaderCallback& callback);

LinkMap* findByName(const std::string_view& name) const noexcept;
Expand All @@ -369,7 +370,7 @@ namespace {
static std::optional<std::string> inSearchPath(std::string_view directories, const std::string_view& name, bool emptyIsCurrentDirectory);
static std::optional<std::string> inCache(const std::string_view& name);

std::optional<std::string> resolvePath(const std::string_view& path) const;
std::optional<std::string> resolvePath(const std::string_view& path, const LinkMap* dlopenCaller) const;
void rememberLibraryDirectory(const std::string& path);

size_t addTlsModule();
Expand Down Expand Up @@ -505,7 +506,7 @@ Loader& Loader::instance() {
return *loader;
}

LinkMap* Loader::load(const std::string_view& requestedPath, int flags) {
LinkMap* Loader::load(const std::string_view& requestedPath, int flags, LinkMap* dlopenCaller) {
std::lock_guard lock(mutex_);

if (requestedPath.empty()) {
Expand All @@ -523,7 +524,7 @@ LinkMap* Loader::load(const std::string_view& requestedPath, int flags) {
return image;
}

auto resolved = resolvePath(requestedPath);
auto resolved = resolvePath(requestedPath, dlopenCaller);

if (!resolved) {
throwError("cannot resolve ELF image: %.*s", static_cast<int>(requestedPath.size()), requestedPath.data());
Expand Down Expand Up @@ -894,6 +895,25 @@ bool Loader::findAddress(const void* address, ElfAddress* res) {
return true;
}

LinkMap* Loader::findImageByAddress(const void* address) {
std::lock_guard lock(mutex_);
auto needle = reinterpret_cast<uintptr_t>(address);
auto found = imagesByAddress_.upper_bound(needle);

if (found == imagesByAddress_.begin()) {
return nullptr;
}
--found;

auto& image = *found->second;

if (needle >= image.mapStart + image.mapSize) {
return nullptr;
}

return &image;
}

int Loader::iterateProgramHeaders(ElfProgramHeaderCallback& callback) {
std::vector<LinkMap*> images;
{
Expand Down Expand Up @@ -1053,18 +1073,22 @@ std::optional<std::string> Loader::inCache(const std::string_view& name) {
return resolved;
}

std::optional<std::string> Loader::resolvePath(const std::string_view& path) const {
std::optional<std::string> Loader::resolvePath(const std::string_view& path, const LinkMap* dlopenCaller) const {
if (path.find('/') != std::string_view::npos) {
return realPath(std::string(path));
}

// A dlopen issued by loaded code searches the calling image's paths;
// while resolving DT_NEEDED, the image being loaded is the requester.
const LinkMap* requester = dlopenCaller ? dlopenCaller : requester_;

if (const auto* configured = getenv("DL_ELF_LIBRARY_PATH"); configured) {
if (auto resolved = inSearchPath(configured, path, false); resolved) {
return resolved;
}
}
if (requester_ && !requester_->rpath.empty()) {
if (auto resolved = inSearchPath(requester_->rpath, path, false); resolved) {
if (requester && !requester->rpath.empty()) {
if (auto resolved = inSearchPath(requester->rpath, path, false); resolved) {
return resolved;
}
}
Expand All @@ -1073,8 +1097,8 @@ std::optional<std::string> Loader::resolvePath(const std::string_view& path) con
return resolved;
}
}
if (requester_ && !requester_->runPath.empty()) {
if (auto resolved = inSearchPath(requester_->runPath, path, false); resolved) {
if (requester && !requester->runPath.empty()) {
if (auto resolved = inSearchPath(requester->runPath, path, false); resolved) {
return resolved;
}
}
Expand Down Expand Up @@ -2091,7 +2115,17 @@ IfaceHandle::Kind ElfImage::handleKind() const {

ElfImage* ElfImage::loadElf(std::string_view path, int flags) {
auto& loader = Loader::instance();
auto* image = loader.load(path, flags);
auto* image = loader.load(path, flags, nullptr);

loader.runPendingInitializers();

return image ? image->wrapper.get() : nullptr;
}

ElfImage* ElfImage::loadElfFrom(const void* callerAddress, std::string_view path, int flags) {
auto& loader = Loader::instance();
auto* caller = callerAddress ? loader.findImageByAddress(callerAddress) : nullptr;
auto* image = loader.load(path, flags, caller);

loader.runPendingInitializers();

Expand Down
3 changes: 3 additions & 0 deletions lib/elf_loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ namespace dyn {
virtual void* lookupVersion(std::string_view symbol, std::string_view version) const = 0;

static ElfImage* loadElf(std::string_view path, int flags);
// The dlopen entry from loaded code: callerAddress names the image
// that issued the call, whose DT_RPATH/DT_RUNPATH join the search.
static ElfImage* loadElfFrom(const void* callerAddress, std::string_view path, int flags);
static bool findAddress(const void* address, ElfAddress* res);
static int iterateProgramHeaders(ElfProgramHeaderCallback& callback);

Expand Down
16 changes: 12 additions & 4 deletions lib/glibc_shim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3370,7 +3370,7 @@ namespace {
return translated;
}

static void* sh_glibc_dlopen(const char* path, int flags) {
static void* sh_glibc_dlopenFrom(const void* caller, const char* path, int flags) {
ThreadTls::current()->clearDlError();

try {
Expand All @@ -3379,7 +3379,7 @@ namespace {
}

auto* provider = runtimeProvider(path);
auto* handle = stub_dlopen(provider ? provider : path, sh_translate_dlopen_flags(flags));
auto* handle = stub_dlopen_from(caller, provider ? provider : path, sh_translate_dlopen_flags(flags));

if (!handle) {
copyStubError("library not found");
Expand All @@ -3396,6 +3396,13 @@ namespace {
return nullptr;
}

// The glibc-boundary entry: its caller's return address names the image
// that issued the dlopen, whose DT_RPATH/DT_RUNPATH join the search for
// names without a slash.
__attribute__((noinline)) static void* sh_glibc_dlopen(const char* path, int flags) {
return sh_glibc_dlopenFrom(__builtin_return_address(0), path, flags);
}

static int sh_dladdr1(const void* address, Dl_info* information, void** extra, int flags) {
if (!stub_dladdr(address, information)) {
return 0;
Expand All @@ -3416,14 +3423,14 @@ namespace {
// The base namespace is plain dlopen; new link-map namespaces stay an
// explicit non-goal, declined through dlerror rather than an abort —
// libcuda imports the symbol.
static void* sh_glibc_dlmopen(long namespace_id, const char* path, int flags) {
__attribute__((noinline)) static void* sh_glibc_dlmopen(long namespace_id, const char* path, int flags) {
if (namespace_id != 0) {
ThreadTls::current()->setDlError("dlmopen: link-map namespaces are not supported");

return nullptr;
}

return sh_glibc_dlopen(path, flags);
return sh_glibc_dlopenFrom(__builtin_return_address(0), path, flags);
}

static void* sh_glibc_dlsym(void* handle, const char* name) {
Expand Down Expand Up @@ -3905,6 +3912,7 @@ namespace {
SH_FUNCTION("posix_memalign", "GLIBC_2.2.5", posix_memalign),
SH_FUNCTION("strcmp", "GLIBC_2.2.5", strcmp),
SH_FUNCTION("dlopen", "GLIBC_2.34", sh_glibc_dlopen),
SH_FUNCTION("dlopen", "GLIBC_2.2.5", sh_glibc_dlopen),
SH_FUNCTION("dlmopen", "GLIBC_2.3.4", sh_glibc_dlmopen),
SH_FUNCTION("__memcpy_chk", "GLIBC_2.3.4", sh_memcpy_chk),
SH_FUNCTION("realpath", "GLIBC_2.3", realpath),
Expand Down
23 changes: 23 additions & 0 deletions tst/glibc_runpath_host_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* Caller-RUNPATH conformance: built with -Wl,-rpath,'$ORIGIN/runpath', the
* host loads its sibling by a bare name that no other search path carries,
* so the load succeeds only when the caller's DT_RUNPATH joins the search. */

extern void* dlopen(const char* filename, int flags);
extern void* dlsym(void* handle, const char* symbol);

#define RTLD_NOW 2
#define RTLD_LOCAL 0

typedef int (*GlibcRunpathValue)(void);

int glibc_runpath_host_value(void) {
void* handle = dlopen("libdlfcn-test-runpath-sibling.so", RTLD_NOW | RTLD_LOCAL);

if (!handle) {
return 0;
}

GlibcRunpathValue value = (GlibcRunpathValue)dlsym(handle, "glibc_runpath_sibling_value");

return value ? value() : 0;
}
5 changes: 5 additions & 0 deletions tst/glibc_runpath_sibling_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* The sibling reachable only through the host's DT_RUNPATH. */

int glibc_runpath_sibling_value(void) {
return 77;
}
59 changes: 59 additions & 0 deletions tst/run_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,65 @@ def main():
],
check=True,
)
# The caller-RUNPATH family: a host carrying -rpath '$ORIGIN/runpath'
# loads a sibling by bare name that sits outside every other search
# path, so the load succeeds only when the caller's DT_RUNPATH joins
# the search.
runpath_directory = library_path / "runpath"
runpath_directory.mkdir()
subprocess.run(
[
*shlex.split(os.environ["DLFCN_CC"]),
"-fPIC",
"-fno-stack-protector",
"-shared",
"-nostdlib",
"-Wl,--no-as-needed",
"-Wl,--enable-new-dtags",
"-Wl,-rpath,$ORIGIN/runpath",
str(root / sysroot_lib / "libc.so.6"),
"-Wl,-soname,libdlfcn-test-runpath-host.so",
os.environ["DLFCN_GLIBC_RUNPATH_HOST_TEST_SOURCE"],
"-o",
str(library_path / "libdlfcn-test-runpath-host.so"),
],
check=True,
)
subprocess.run(
[
*shlex.split(os.environ["DLFCN_CC"]),
"-fPIC",
"-fno-stack-protector",
"-shared",
"-nostdlib",
"-Wl,--no-as-needed",
str(root / sysroot_lib / "libc.so.6"),
"-Wl,-soname,libdlfcn-test-runpath-sibling.so",
os.environ["DLFCN_GLIBC_RUNPATH_SIBLING_TEST_SOURCE"],
"-o",
str(runpath_directory / "libdlfcn-test-runpath-sibling.so"),
],
check=True,
)
# The same host with old-dtags: a caller carrying DT_RPATH only.
subprocess.run(
[
*shlex.split(os.environ["DLFCN_CC"]),
"-fPIC",
"-fno-stack-protector",
"-shared",
"-nostdlib",
"-Wl,--no-as-needed",
"-Wl,--disable-new-dtags",
"-Wl,-rpath,$ORIGIN/runpath",
str(root / sysroot_lib / "libc.so.6"),
"-Wl,-soname,libdlfcn-test-rpath-host.so",
os.environ["DLFCN_GLIBC_RUNPATH_HOST_TEST_SOURCE"],
"-o",
str(library_path / "libdlfcn-test-rpath-host.so"),
],
check=True,
)
(library_path / "libdlfcn-test-pci.so").symlink_to(
root / sysroot_lib / "libpciaccess.so.0"
)
Expand Down
26 changes: 26 additions & 0 deletions tst/smoke.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,32 @@ int main() {
return 1;
}

// A dlopen issued by loaded code searches the caller's DT_RUNPATH: the
// host carries -rpath '$ORIGIN/runpath' and loads a sibling that sits
// outside every other search path.
auto* runpathHost = stub_dlopen("libdlfcn-test-runpath-host.so", RTLD_NOW | RTLD_LOCAL);
if (!runpathHost) {
fprintf(stderr, "runpath host load failed: %s\n", stub_dlerror());
return 1;
}
auto runpathHostValue = reinterpret_cast<GlibcTest>(requiredSymbol(runpathHost, "glibc_runpath_host_value"));
if (!runpathHostValue || runpathHostValue() != 77) {
fprintf(stderr, "caller DT_RUNPATH lookup failed\n");
return 1;
}

// The old-dtags counterpart: a caller carrying DT_RPATH only.
auto* rpathHost = stub_dlopen("libdlfcn-test-rpath-host.so", RTLD_NOW | RTLD_LOCAL);
if (!rpathHost) {
fprintf(stderr, "rpath host load failed: %s\n", stub_dlerror());
return 1;
}
auto rpathHostValue = reinterpret_cast<GlibcTest>(requiredSymbol(rpathHost, "glibc_runpath_host_value"));
if (!rpathHostValue || rpathHostValue() != 77) {
fprintf(stderr, "caller DT_RPATH lookup failed\n");
return 1;
}

// Initial-exec TLS: one process-wide GOT offset must be valid in every
// thread through the surplus static arena. The parked thread exists
// before the module loads and must see zeroed TLS for it.
Expand Down