嵌套共享库中的系统功能超载

发布于 2025-02-04 21:16:38 字数 2642 浏览 2 评论 0原文

简单的方案

  1. 应用程序使用WRITE来自libc的函数,并链接到共享库。
  2. 共享库定义函数。
  3. 原始Write libc的函数将由其版本从共享库中重载。

嵌套方案

  1. 应用程序使用WRITE功能从libc
  2. 共享库1并未定义其自己的,但取决于共享库2。
  3. 共享库2定义write函数。
  4. 编号写入函数将不是被第二个共享库中的版本替换。

我想明白为什么?如何使其与嵌套的共享库依赖关系一起使用。

这是确切的代码示例:

main.c

#include <unistd.h>

int main() {

    write(1,"Hello\n",6);
    return 0;
}

shared-lib-1.c

#include <unistd.h>

__attribute__((constructor))
void shared_lib_1_constructor(void) {
    char str[] = "shared-lib-1-constructor\n";
    write(1, str, sizeof(str)); 
}

共享lib-2.c

#include <stdlib.h>
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count) {
    exit(1);
    return 0;
}

__attribute__((constructor))
void shared_lib_2_constructor(void) {
    char str[] = "shared-lib-2-constructor\n";
    write(1, str, sizeof(str)); 
}

build.sh

#!/usr/bin/env sh

set -v
set -e

gcc -g -fPIC -shared shared-lib-2.c -o libshared-lib-2.so
gcc -g -fPIC -shared -Wl,-rpath . -L`pwd` -lshared-lib-2 shared-lib-1.c -o libshared-lib-1.so
gcc -g -L`pwd` -Wl,-rpath . -lshared-lib-1 main.c

a.out给出:

[smirnov@localhost tmp]$ ./a.out 
shared-lib-2-constructor
shared-lib-1-constructor
Hello

write> write是不被共享lib-2覆盖。即使从共享LIB-2代码中也完全忽略了它。

它如何与嵌套库一起使用?移动写入定义为shared-lib-1 工作,它超载glibc版本,从应用程序退出。

使用LD_DEBUG = ALL显示解决方案的运行应用程序:

   36441:     Initial object scopes
   36441:     object=./a.out [0]
   36441:      scope 0: ./a.out ./libshared-lib-1.so /lib64/libc.so.6 ./libshared-lib-2.so /lib64/ld-linux-x86-64.so.2
...
   36441:     calling init: ./libshared-lib-2.so
   36441:     
   36441:     symbol=write;  lookup in file=./a.out [0]
   36441:     symbol=write;  lookup in file=./libshared-lib-1.so [0]
   36441:     symbol=write;  lookup in file=/lib64/libc.so.6 [0]
   36441:     binding file ./libshared-lib-2.so [0] to /lib64/libc.so.6 [0]: normal symbol `write'

为什么libc放置在共享lib-1和共享lib-2之间? 看来ld按以下顺序解决:

  • A.Out的所有依赖性,但不是递归的,只有第一个级别的
  • 所有亚依赖性,但不是recursivelly
  • so y ...

我发现的唯一解决方案是要使用

LD_DYNAMIC_WEAK=1 ./a.out

是否有办法预先修复行为?

Simple scenario

  1. Application uses write function from libc and links to shared library.
  2. Shared library defines write function.
  3. Original write function from libc will be overloaded by its version from the shared library.

Nested scenario

  1. Application uses write function from libc.
  2. Shared library 1 does not defines its own write but depends on shared library 2.
  3. Shared library 2 defines write function.
  4. No. write function will NOT be replaced with a version from the second shared library.

I do want to understand Why so ? How to make it work with nested shared library dependencies.

Here is exact code examples:

main.c

#include <unistd.h>

int main() {

    write(1,"Hello\n",6);
    return 0;
}

shared-lib-1.c

#include <unistd.h>

__attribute__((constructor))
void shared_lib_1_constructor(void) {
    char str[] = "shared-lib-1-constructor\n";
    write(1, str, sizeof(str)); 
}

shared-lib-2.c

#include <stdlib.h>
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count) {
    exit(1);
    return 0;
}

__attribute__((constructor))
void shared_lib_2_constructor(void) {
    char str[] = "shared-lib-2-constructor\n";
    write(1, str, sizeof(str)); 
}

build.sh

#!/usr/bin/env sh

set -v
set -e

gcc -g -fPIC -shared shared-lib-2.c -o libshared-lib-2.so
gcc -g -fPIC -shared -Wl,-rpath . -L`pwd` -lshared-lib-2 shared-lib-1.c -o libshared-lib-1.so
gcc -g -L`pwd` -Wl,-rpath . -lshared-lib-1 main.c

Execution of the a.out gives:

[smirnov@localhost tmp]$ ./a.out 
shared-lib-2-constructor
shared-lib-1-constructor
Hello

write was not overwritten by shared-lib-2. It was completely ignored even from shared-lib-2 code.

How does it work with nested libraries ? Moving write definition to shared-lib-1 does work, it overloads glibc version and exits from application.

Running application with LD_DEBUG=all shows order of resolving:

   36441:     Initial object scopes
   36441:     object=./a.out [0]
   36441:      scope 0: ./a.out ./libshared-lib-1.so /lib64/libc.so.6 ./libshared-lib-2.so /lib64/ld-linux-x86-64.so.2
...
   36441:     calling init: ./libshared-lib-2.so
   36441:     
   36441:     symbol=write;  lookup in file=./a.out [0]
   36441:     symbol=write;  lookup in file=./libshared-lib-1.so [0]
   36441:     symbol=write;  lookup in file=/lib64/libc.so.6 [0]
   36441:     binding file ./libshared-lib-2.so [0] to /lib64/libc.so.6 [0]: normal symbol `write'

Why libc placed in between shared-lib-1 and shared-lib-2 ?
It seems that ld resolves in the following order:

  • all dependencies of a.out, but not recursivelly, only first level
  • all subdependencies, but not recursivelly
  • so on...

The only solution I've found is to use

LD_DYNAMIC_WEAK=1 ./a.out

Is there a way to fix behavior in advance ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

幸福丶如此 2025-02-11 21:16:38

写入功能将不会被第二个共享库中的版本替换。

这不是我所期望的,我花了一段时间才弄清楚为什么会发生。

问题是libc.so.6不定义函数(如果这样做,无论依赖关系是否是直接的,一切都将工作相同)。

相反,libc.so.6定义版本为 写@@ glibc_2.2.5符号。正是符号版本(GNU扩展)会干扰您所需的结果。

比较:

gcc main.c ./libshared-lib-1.so && nm -D a.out | grep ' write'
                 U write@GLIBC_2.2.5
gcc main.c ./libshared-lib-2.so && nm -D a.out | grep ' write'
                 U write

执行符号分辨率时,加载程序在搜索范围中查找符号版本(A.Out在正面)。使用libshared-lib-1.so,加载程序发现它应该寻找write> write版本glibc_2.2.2.5(只能找到它在libc.so.6)中,忽略了libshared-lib-2.so中未反之的定义。

有没有办法预先修复行为?

如果您可以控制应用程序链接行,则最好使用a.out使用libshared-lib-2.so

gcc main.c ./libshared-lib-1.so ./libshared-lib-2.so
./a.out
# no output as expected.

如果您无法控制应用程序链接行,您应该能够使用链接器- 版本 - script flag和适当的版本脚本提供版本的write@glibc_2.2.5。但是我最初的尝试失败了,我不确定是否可以使这种方法起作用。

write function will NOT be replaced with a version from the second shared library.

This is not what I expected, and it took me a while to figure out why it's happening.

The issue is that libc.so.6 doesn't define write function (if it did, everything would have worked the same whether the dependency is direct or not).

Instead, libc.so.6 defines a versioned write@@GLIBC_2.2.5 symbol. It is that symbol versioning (a GNU extension) which interferes with your desired outcome.

Compare:

gcc main.c ./libshared-lib-1.so && nm -D a.out | grep ' write'
                 U write@GLIBC_2.2.5
gcc main.c ./libshared-lib-2.so && nm -D a.out | grep ' write'
                 U write

When performing symbol resolution, the loader looks up symbol version in the search scope (and a.out is at the front). With libshared-lib-1.so, the loader finds that it should be looking for write version GLIBC_2.2.5 (which can only be found in libc.so.6), and ignores the un-versioned definition in libshared-lib-2.so.

Is there a way to fix behavior in advance ?

If you can control the application link line, it's best to link a.out with libshared-lib-2.so:

gcc main.c ./libshared-lib-1.so ./libshared-lib-2.so
./a.out
# no output as expected.

If you can't control the application link line, you should be able to provide a versioned write@GLIBC_2.2.5 using the linker --version-script flag and appropriate version script. But my initial attempt at doing that failed, and I am not sure this approach can be made to work.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文