Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading executable or executing a library

Tags:

There is a large number of questions on SO about how to execute a library or dynamically load an executable. As far as I can tell, all the answers come down to: compile your executable as position-independent code and load it with dlopen. This worked great --- and still works great on macOS --- until a recent change in glibc, which explicitly disabled dlopening PIEs. This change is now in the current version of glibc (2.30) on ArchLinux, for example, and trying to dlopen a position-independent executable gives an error: "cannot dynamically load position-independent executable".

It's difficult to guess what prompted such a radical change that breaks so much code and useful use cases. (The explanations on Patchwork and Bugzilla don't make much sense to me.) But there is now a question: what to do if you want to create an executable that's also a dynamic library, or vice versa?

A solution was linked from one of the comments. Reproducing it here for posterity:

#include <stdio.h>
#include <unistd.h>

const char service_interp[] __attribute__((section(".interp"))) = "/lib/ld-linux-x86-64.so.2";

extern "C" {

void lib_entry(void)
{
  printf("Entry point of the service library\n");    
  _exit(0);
}

}

Compiling with g++ -shared test-no-pie.cpp -o test-no-pie -Wl,-e,lib_entry produces a shared object (dynamic library) that can also be executed on Linux.

I have two questions:

  1. What if I want to pass command-line arguments? How to modify this solution so it accepts arc,argv?
  2. Are there other alternatives?
like image 418
foxcub Avatar asked Nov 27 '19 16:11

foxcub


2 Answers

It's difficult to guess what prompted such a radical change

Not really: it never worked correctly.

that breaks so much code

That code was broken already in subtle ways. Now you get a clear indication that it will not work.

Are there other alternatives?

Don't do that?

What problem does dlopening an executable solve?

If it's a real problem, open a GLIBC bugzilla feature request, explaining that problem and requesting a supported mechanism to achieve desired result.

Update:

at least say why "it never worked correctly". Is it some triviality like potentially clashing globals between the executables, or something real?

Thread-local variables is an example that doesn't work correctly. Whether you think they are "real" or not I have no idea.

Here is the code:

// foo.c
#include <stdio.h>

__thread int var;

__attribute__((constructor))
static void init()
{
  var = 42;
  printf("foo.c init: %d %p\n", var, &var);
}

int bar() {
  printf("foo.c bar:  %d %p\n", var, &var);
  return var;
}

int main()
{
  printf("foo.c main: %d %p bar()=%d\n", var, &var, bar());
  return 0;
}
gcc -g foo.c -o foo -Wl,-E -fpie -pie && ./foo
foo.c init: 42 0x7fb5dfd7d4fc
foo.c bar:  42 0x7fb5dfd7d4fc
foo.c main: 42 0x7fb5dfd7d4fc bar()=42
// main.c
// Error checking omitted for brevity
#include <dlfcn.h>
#include <stdio.h>

int main()
{
  void *h1 = dlopen("./foo", RTLD_LOCAL|RTLD_LAZY);
  int (*bar)(void) = dlsym(h1, "bar");

  printf("main.c: %d\n", bar());
  return 0;
}
gcc -g main.c -ldl && ./a.out
foo.c init: 42 0x7fb7305da73c
foo.c bar:  0 0x7fb7305da73c    <<< what?
main.c: 0                       <<< what?

This is using GNU C Library (Debian GLIBC 2.28-10) stable release version 2.28.

Bottom line: this was never designed to work, and you just happened to not step on many of the land-mines, so you thought it is working, when in fact you were exercising undefined behavior.

like image 101
Employed Russian Avatar answered Sep 30 '22 20:09

Employed Russian


Please see this answer:

https://stackoverflow.com/a/68339111/14760867

The argc, argv question is not answered there, but when I found I needed one, I hacked something together to parse /proc/self/cmdline at runtime for pam_cap.so use.

like image 37
Andrew G Morgan Avatar answered Sep 30 '22 21:09

Andrew G Morgan