Примечание. Предполагаемый обман никоим образом НЕ отвечает на этот вопрос; в частности, локальный пример потока можно легко исправить, скомпилировав его с помощью -fPIC
, точно так же, как уже упомянутый ниже пример bugzilla. А в остальном просто непрошенные мнения и безосновательные претензии.
В последних системах Linux, таких как Debian 10, RHEL 8 и т. д., вы можете создать файл ELF, который одновременно является независимым от позиции исполняемым файлом (PIE) и динамически загружаемой библиотекой/общим объектом.
Это очень полезно и имеет множество применений, таких как, например. создание программ-оболочек, которые предварительно загружают сами себя, а затем выполняют другую программу (см. Пример 2 ниже), или наличие виртуальной машины/языковой среды в виде единого объекта, который может быть либо встроен, либо работать как автономный интерпретатор. И все это без необходимости иметь дело с жесткими установочными каталогами, эксплойтами символических ссылок, $ORIGIN
s или другими подобными кошмарами безопасности и политики файловой системы.
Однако изменение 2c75b54 в glibc сломало его:
elf: Отказаться открывать объекты PIE [BZ #24323]
Другой исполняемый файл уже сопоставлен, поэтому динамический компоновщик не может правильно выполнить перемещение для второго исполняемого файла.
также утверждал в обсуждении, что привело к этому:
Также нет возможности правильно выполнить конструкторы ELF второго исполняемого файла.
Но это кажется фикцией. Как показано на Приложении 1 ниже, конструкторы, перемещения и локальные переменные потока работают нормально.
Перемещения копирования не работают, но есть ли причина также ломать программы, которые не используют перемещения копирования, как те, которые скомпилированы с помощью -fPIC
? (В частности, тестовый пример из этого обсуждение можно легко исправить, просто скомпилировав его с помощью -fPIC
).
Это изменение также было выбрано во FreeBSD 12.2, основная причина в том, что это делает glibc. слишком. Он по-прежнему работает в NetBSD 9.1 и OpenBSD 6.8 (хотя конструкторы не работают в OpenBSD).
Итак, каковы технические причины, по которым использование PIE таким образом не должно работать? Четкие сценарии, в которых это может сломаться (см. проблемы из рисунков 1 и 2), были бы замечательными.
Экспонат 1
Программа libexe
должна работать так же, когда a) выполняется напрямую или b) dl-загружается как разделяемая библиотека и вызывается loader
через функцию main
.
Задача состоит в том, чтобы продемонстрировать возможности, которые libexe
мог бы использовать, но которые препятствовали бы либо a), либо b) или заставляли бы его работать по-другому, что было бы трудно исправить.
cat <<'EOT' > libexe.c
#include <stdio.h>
#include <errno.h>
#include <err.h>
__thread int var;
void set_errno(int e){ errno = e; }
__attribute__((weak))
int main(void){
set_errno(EPIPE); warn("%s var=%d", __FILE__, var);
}
__attribute__((constructor))
static void init(void){
var = 33;
fprintf(stderr, "%s's constructor\n", __FILE__);
}
EOT
cat <<'EOT' > loader.c
#include <dlfcn.h>
#include <stdio.h>
#include <err.h>
#include <errno.h>
int main(void){
void *dl; char *lib = "./libexe";
if(!(dl = dlopen(lib, RTLD_LAZY)))
errx(1, "dlopen: %s", dlerror());
printf("var=%d in %s\n", *(int*)dlsym(dl, "var"), __FILE__);
((void(*)(int))dlsym(dl, "set_errno"))(EBADF); warn("%s", __FILE__);
return ((int(*)(void))dlsym(dl, "main"))();
}
__attribute__((constructor))
static void init(void){
fprintf(stderr, "%s's constructor\n", __FILE__);
}
EOT
cc -pie -fPIC -Wl,-E libexe.c -o libexe
cc loader.c -o loader -ldl
############
$ ./loader
loader.c's constructor
libexe.c's constructor
var=33 in loader.c
loader: loader.c: Bad file descriptor
loader: libexe.c var=33: Broken pipe
Экспонат 2
Эта небольшая программа предварительно загружает себя, а затем запускает другой исполняемый файл. В отличие от типичного LD_PRELOAD=/some/path ./cmd
, это также будет нормально работать через fexecve
. /execveat(AT_EMPTY_PATH)
в Linux, как при выполнении < файл href="https://man7.org/linux/man-pages/man2/memfd_create.2.html" rel="nofollow noreferrer">memfd_create
d. Он был протестирован для работы с Debian ›= 9, Centos/RHEL ›= 7 и т. д.
Задача состоит в том, чтобы продемонстрировать исполняемый файл, который потерпит неудачу при последовательном выполнении таким образом, но будет работать нормально, когда предварительно загруженный код находится в отдельной библиотеке, как в случае с stdbuf
/libstdbuf.so
.
cat <<'EOT' > read-eio.c
#define _GNU_SOURCE
#include <unistd.h>
#include <err.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
int main(int ac, char **av){
int fd; char buf[32];
if(ac < 2 || !av[1])
errx(1, "usage: %s cmd args..", av[0]);
if((fd = open("/proc/self/exe", O_PATH)) == -1)
err(1, "open /proc/self/exe");
snprintf(buf, sizeof buf, "/dev/fd/%d", fd);
if(setenv("LD_PRELOAD", buf, 1))
err(1, "setenv");
execvp(av[1], av + 1);
err(1, "execvp %s", av[1]);
}
ssize_t read(int fd, void *b, size_t z){
errno = EIO; return -1;
}
EOT
cc -fPIC -pie read-eio.c -o read-eio
###########
$ ./read-eio cat
cat: -: Input/output error
-fPIC -shared
, также пригодной для использования в качестве исполняемого файла. Кто они такие? - person Impudent Snob   schedule 04.11.2020