• Re: (interposers): How to workaround the "strong symbols" problem?

    From Marcel Mueller@21:1/5 to All on Sun Jan 19 16:08:32 2025
    Am 18.01.25 um 01:42 schrieb Kenny McCormack:
    Right. It seems to boil down to:

    You can't interpose a syscall.

    I.e., so if a library function calls the syscall directly (rather than
    going through the Glibc wrapper), you can't interpose it. This is, of course, as it should be.

    Hmm, it may be related to optimizations. Assuming the openat call is
    inlined in another compilation unit there is no option to change this at
    link time.


    Marcel

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Kettlewell@21:1/5 to Marcel Mueller on Sun Jan 19 16:04:33 2025
    Marcel Mueller <news.5.maazl@spamgourmet.org> writes:
    Am 18.01.25 um 01:42 schrieb Kenny McCormack:
    Right. It seems to boil down to:
    You can't interpose a syscall.
    I.e., so if a library function calls the syscall directly (rather
    than going through the Glibc wrapper), you can't interpose it. This
    is, of course, as it should be.

    Hmm, it may be related to optimizations. Assuming the openat call is
    inlined in another compilation unit there is no option to change this
    at link time.

    Inlining and compiler optimization aren’t relevant here.

    “System call” can mean two things:

    1) The actual transfer to the kernel, via an instruction such as SYSCALL
    (x86) or SVC (Arm). This is what appears in strace.
    2) The function in the C library that contains this instruction.

    Normally the distinction is irrelevant, but here it matters.

    “You can’t interpose a syscall”, is only true for sense 1. That instruction is somewhere the middle of a function and the interposition technique he’s using relies on runtime symbol resolution rules, so it doesn’t apply to sense-1 syscalls.

    It’s not true in sense 2. If you write an interposition library with the symbol open(), and use LD_PRELOAD to insert it into a program that calls open(), then the interposition will succeed. The output I quoted in my
    previous posting shows this happening.


    However there are several further issues.


    Firstly, normally sense 1 and sense 2 amount to the same underlying
    system call. The function openat() will call the openat system call. But
    they don’t have to be, and indeed in the case at hand they’re not: in
    Glibc the function open() will call the openat system call, not the open
    system call:
    https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/open.c

    That’s why, if you strace a program that uses open(), you still see
    openat() in the trace output. (Even in the absence of any further
    obstacles) an attempt to use ELF interposition on openat would not work
    in this case, but (again in the absence of further obstcales)
    interposing open would do the job.


    Secondly, if you follow the call chain from stdio functions like fopen
    (or fopen64) then the point where it calls open is via the name
    __open, not open. (Both names are aliases for __libc_open.) So,
    interposing the name open won’t work (on this call); one would have to interpose __open instead.


    Thirdly, however, there is a final obstacle. __open is a hidden alias
    for __libc_open. The effect of this is that calls to the name __open the originate inside Glibc don’t participate in runtime symbol resolution so cannot be interposed. See the discussion of PLT bypassing in: https://sourceware.org/git/?p=glibc.git;a=blob;f=include/libc-symbols.h


    So “you can’t interpose syscalls” is true in one sense, but not in the sense that matters. The thing that got in Kenny’s way is the PLT bypass logic.

    --
    https://www.greenend.org.uk/rjk/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kenny McCormack@21:1/5 to invalid@invalid.invalid on Sun Jan 19 17:12:12 2025
    In article <wwvsepessby.fsf@LkoBDZeT.terraraq.uk>,
    Richard Kettlewell <invalid@invalid.invalid> wrote:
    ...
    System call can mean two things:

    1) The actual transfer to the kernel, via an instruction such as SYSCALL
    (x86) or SVC (Arm). This is what appears in strace.
    2) The function in the C library that contains this instruction.

    Normally the distinction is irrelevant, but here it matters.

    You cant interpose a syscall, is only true for sense 1. That
    instruction is somewhere the middle of a function and the interposition >technique hes using relies on runtime symbol resolution rules, so it
    doesnt apply to sense-1 syscalls.

    When I wrote "syscall", I meant (surprisingly enough) syscall.
    What you are calling "sense #1".

    So, what I wrote is correct.

    What you are calling "sense #2" (i.e., the "glibc wrapper" that is provided
    for most (not all) syscalls) is just another function and can, of course,
    be interposed.

    --
    Watching ConservaLoons playing with statistics and facts is like watching a newborn play with a computer. Endlessly amusing, but totally unproductive.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Marcel Mueller@21:1/5 to All on Sun Jan 19 22:19:08 2025
    Am 19.01.25 um 18:12 schrieb Kenny McCormack:
    What you are calling "sense #2" (i.e., the "glibc wrapper" that is provided for most (not all) syscalls) is just another function and can, of course,
    be interposed.

    Not necessarily.
    The header files might contain information that tells the compiler to
    inline the wrapper when possible. In this case it won't succeed either.


    Marcel

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kenny McCormack@21:1/5 to news.5.maazl@spamgourmet.org on Sun Jan 19 22:05:10 2025
    In article <vmjq8b$2clu1$1@gwaiyur.mb-net.net>,
    Marcel Mueller <news.5.maazl@spamgourmet.org> wrote:
    Am 19.01.25 um 18:12 schrieb Kenny McCormack:
    What you are calling "sense #2" (i.e., the "glibc wrapper" that is provided >> for most (not all) syscalls) is just another function and can, of course,
    be interposed.

    Not necessarily.
    The header files might contain information that tells the compiler to
    inline the wrapper when possible. In this case it won't succeed either.

    In that case, it's not a wrapper, now is it?

    We're just arguing over definitions at this point.

    --
    1) The only professionals who refer to their customers as "users" are
    computer guys and drug dealers.
    2) The only professionals who refer to their customers as "clients" are
    lawyers and prostitutes.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Marcel Mueller@21:1/5 to All on Mon Jan 20 09:45:52 2025
    Am 19.01.25 um 23:05 schrieb Kenny McCormack:
    In article <vmjq8b$2clu1$1@gwaiyur.mb-net.net>,
    Marcel Mueller <news.5.maazl@spamgourmet.org> wrote:
    Not necessarily.
    The header files might contain information that tells the compiler to
    inline the wrapper when possible. In this case it won't succeed either.

    In that case, it's not a wrapper, now is it?

    We're just arguing over definitions at this point.

    It seems so.

    Inline functions are always normal functions too, e.g. if you take the
    address of. It might also depend on some constant arguments whether
    inlining takes place or not. How do you call it when a "wrapper"
    function is modeled this way?
    AFAIK there is really only one thing you cannot do with this kind of implementation: Use an interposer.


    Marcel

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Kettlewell@21:1/5 to Marcel Mueller on Mon Jan 20 08:44:58 2025
    Marcel Mueller <news.5.maazl@spamgourmet.org> writes:
    Am 19.01.25 um 18:12 schrieb Kenny McCormack:
    What you are calling "sense #2" (i.e., the "glibc wrapper" that is provided >> for most (not all) syscalls) is just another function and can, of course,
    be interposed.

    Not necessarily.

    Correct; see my earlier post for the real reasons.

    The header files might contain information that tells the compiler to
    inline the wrapper when possible. In this case it won't succeed
    either.

    In theory it might, but in reality it doesn’t.

    --
    https://www.greenend.org.uk/rjk/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Muttley@DastardlyHQ.org@21:1/5 to All on Mon Jan 20 09:41:41 2025
    On Sun, 19 Jan 2025 16:04:33 +0000
    Richard Kettlewell <invalid@invalid.invalid> wibbled:
    Marcel Mueller <news.5.maazl@spamgourmet.org> writes:
    Am 18.01.25 um 01:42 schrieb Kenny McCormack:
    Right. It seems to boil down to:
    You can't interpose a syscall.
    I.e., so if a library function calls the syscall directly (rather
    than going through the Glibc wrapper), you can't interpose it. This
    is, of course, as it should be.

    Hmm, it may be related to optimizations. Assuming the openat call is
    inlined in another compilation unit there is no option to change this
    at link time.

    Inlining and compiler optimization aren’t relevant here.

    “System call” can mean two things:

    1) The actual transfer to the kernel, via an instruction such as SYSCALL
    (x86) or SVC (Arm). This is what appears in strace.

    On a slight tangent, does anyone know of a good reference for how to use
    the ptrace() call on Linux? The man page is somewhat obtuse.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kalevi Kolttonen@21:1/5 to Muttley@dastardlyhq.org on Mon Jan 20 14:29:45 2025
    Muttley@dastardlyhq.org wrote:
    On a slight tangent, does anyone know of a good
    reference for how to use the ptrace() call on
    Linux? The man page is somewhat obtuse.

    Just guessing, but I can only think of reading
    gdb source code.

    br,
    KK

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kenny McCormack@21:1/5 to Kalevi Kolttonen on Mon Jan 20 19:30:32 2025
    In article <vmlmkp$36kmo$1@dont-email.me>,
    Kalevi Kolttonen <kalevi@kolttonen.fi> wrote:
    Muttley@dastardlyhq.org wrote:
    On a slight tangent, does anyone know of a good
    reference for how to use the ptrace() call on
    Linux? The man page is somewhat obtuse.

    Just guessing, but I can only think of reading
    gdb source code.

    Or strace.
    --
    The randomly chosen signature file that would have appeared here is more than 4 lines long. As such, it violates one or more Usenet RFCs. In order to remain in compliance with said RFCs, the actual sig can be found at the following URL:
    http://user.xmission.com/~gazelle/Sigs/Infallibility

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Kenny McCormack on Mon Jan 20 19:42:17 2025
    On 2025-01-20, Kenny McCormack <gazelle@shell.xmission.com> wrote:
    In article <vmlmkp$36kmo$1@dont-email.me>,
    Kalevi Kolttonen <kalevi@kolttonen.fi> wrote:
    Muttley@dastardlyhq.org wrote:
    On a slight tangent, does anyone know of a good
    reference for how to use the ptrace() call on
    Linux? The man page is somewhat obtuse.

    Just guessing, but I can only think of reading
    gdb source code.

    Or strace.

    Since ptrace is something implemented in the kernel, a peek at that
    could also be useful, where the documentation is lacking.

    This site is very nice for hyperlinked, cross-indexed browsing,
    featuring pretty much every version of the kernel, ever,
    from linux 0.01 to current (6.13.x, presently).

    https://elixir.bootlin.com/

    You can jump to uses and definitions of identifiers and such.
    I use this as a resource in the context of embedded work.

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kenny McCormack@21:1/5 to All on Fri Jan 17 21:33:45 2025
    Context is Linux (and only Linux).

    Over the years, I have written many "interposers" - that is, a shared
    library loaded with LD_PRELOAD that hooks some system or library call
    (e.g., "read"). The interposer usually ends up calling the "real"
    function, then doing something special either before or after the call.

    Generally, it all works fine - or at least, it did - until they started
    having "strong symbols" (I think that's the right term). Anyway, some
    number of years back, I noticed that it became kind of hit and miss as to whether or not you could get your hooked version of the function to be
    called. Generally, it seemed, the more "low level" the function, the less likely it was that the interposer would work.

    So, I am wondering, is there a fix for this? I'm assuming that somebody decided that interposers were evil and thus, they came up with this as a
    way to foil us, but there should be fix to the fix, so to speak. Is there?

    Note: I am not showing code at the moment, because I'd like (if possible) a simple "Yes, it can be done" or "No, they got you" type answer. If there
    is sufficient interest, I can post code in a followup.

    --
    Marshall: 10/22/51
    Jessica: 4/4/79

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kenny McCormack@21:1/5 to Kenny McCormack on Fri Jan 17 21:53:32 2025
    In article <vmeibp$33sam$1@news.xmission.com>,
    Kenny McCormack <gazelle@shell.xmission.com> wrote:
    Context is Linux (and only Linux).

    Over the years, I have written many "interposers" - that is, a shared
    library loaded with LD_PRELOAD that hooks some system or library call
    (e.g., "read"). The interposer usually ends up calling the "real"
    function, then doing something special either before or after the call.

    Also, wanted to state that the function I am trying to hook is "openat".

    "open" I can hook, but "openat" doesn't work. I think most programs just
    call "open", which eventually ends up calling "openat", so that (hooking "open") works. But some programs call "openat" directly, and that doesn't work.

    And, also, just to clarify, the whole point of this is to modify the
    behavior of a program without the bother of re-compiling it.

    --
    The randomly chosen signature file that would have appeared here is more than 4 lines long. As such, it violates one or more Usenet RFCs. In order to remain in compliance with said RFCs, the actual sig can be found at the following URL:
    http://user.xmission.com/~gazelle/Sigs/Pearls

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Richard Kettlewell@21:1/5 to Kenny McCormack on Fri Jan 17 23:10:10 2025
    gazelle@shell.xmission.com (Kenny McCormack) writes:
    Context is Linux (and only Linux).

    Glibc? If so what distribution and what version?

    Over the years, I have written many "interposers" - that is, a shared
    library loaded with LD_PRELOAD that hooks some system or library call
    (e.g., "read"). The interposer usually ends up calling the "real"
    function, then doing something special either before or after the
    call.

    Generally, it all works fine - or at least, it did - until they
    started having "strong symbols" (I think that's the right term).

    I think you mean STB_WEAK vs STB_GLOBAL, in the naming used in Glibc and
    the ELF spec.

    Anyway, some number of years back, I noticed that it became kind of
    hit and miss as to whether or not you could get your hooked version of
    the function to be called. Generally, it seemed, the more "low level"
    the function, the less likely it was that the interposer would work.

    So, I am wondering, is there a fix for this? I'm assuming that
    somebody decided that interposers were evil and thus, they came up
    with this as a way to foil us, but there should be fix to the fix, so
    to speak. Is there?

    I can interpose both weak and global symbols from Debian’s Glibc 2.39
    without doing anything unusual.

    Note: I am not showing code at the moment, because I'd like (if
    possible) a simple "Yes, it can be done" or "No, they got you" type
    answer. If there is sufficient interest, I can post code in a
    followup.

    Yes, it can be done.

    $ make check
    gcc -Wall -Wextra -Wno-unused -Werror -o t.so -shared t.c -ldl -lc
    gcc -Wall -Wextra -Wno-unused -Werror -o u u.c
    readelf -Ts /lib/x86_64-linux-gnu/libc.so.6 | grep -wE 'open|openat|memfrob'
    1701: 00000000000f7f30 296 FUNC WEAK DEFAULT 16 open@@GLIBC_2.2.5
    2274: 00000000000f80c0 280 FUNC WEAK DEFAULT 16 openat@@GLIBC_2.4
    3039: 000000000009bf40 30 FUNC GLOBAL DEFAULT 16 memfrob@@GLIBC_2.2.5
    LD_PRELOAD=./t.so ./u memfrob
    interposed memfrob
    0x7ffc04c4560f
    LD_PRELOAD=./t.so ./u open /dev/null
    interposed open
    3
    LD_PRELOAD=./t.so ./u openat /dev/null
    interposed openat
    3

    My interposing versions are not doing anything special. Representative
    example:

    void *memfrob(void *s, size_t n) {
    void *(*real_memfrob)(void *s, size_t n);
    real_memfrob = dlsym(RTLD_NEXT, "memfrob");
    write(2, "interposed memfrob\n", 19);
    return real_memfrob(s, n);
    }

    Something tha tripped me up: a program shows a call to openat() in
    strace output, so one tries to interpose openat(). But in fact the
    program is calling the Glibc open() function, which calls the openat()
    syscall.

    $ strace -etrace=open,openat cat /dev/null
    openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/dev/null", O_RDONLY) = 3
    +++ exited with 0 +++
    $ LD_PRELOAD=./t.so cat /dev/null
    interposed open

    However I think from your other post that you’ve already considered this possibility.

    --
    https://www.greenend.org.uk/rjk/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kenny McCormack@21:1/5 to invalid@invalid.invalid on Sat Jan 18 00:42:35 2025
    In article <wwved112g19.fsf@LkoBDZeT.terraraq.uk>,
    Richard Kettlewell <invalid@invalid.invalid> wrote:
    ...
    Generally, it all works fine - or at least, it did - until they
    started having "strong symbols" (I think that's the right term).

    I think you mean STB_WEAK vs STB_GLOBAL, in the naming used in Glibc and
    the ELF spec.

    Maybe so. I am not familiar with these terms. Could you explain further?

    Anyway, and FWIW, I think it turns out this is not relevant to my problem
    (see below).

    ...

    I can interpose both weak and global symbols from Debians Glibc 2.39
    without doing anything unusual.

    Good to hear. So, does this (weak vs. global) have any connection at all
    to whether or not you can interpose them?

    ...

    Yes, it can be done.
    ...
    Something that tripped me up: a program shows a call to openat() in
    strace output, so one tries to interpose openat(). But in fact the
    program is calling the Glibc open() function, which calls the openat() >syscall.
    ...
    However I think from your other post that you've already considered this >possibility.

    Right. It seems to boil down to:

    You can't interpose a syscall.

    I.e., so if a library function calls the syscall directly (rather than
    going through the Glibc wrapper), you can't interpose it. This is, of
    course, as it should be.

    Anyway, I got it working (so this thread can be considered solved) and I
    think you gave me a push in the right direction. It turns out the function
    I have to hook is "fopen64". I figured this out by using "ltrace" rather
    than "strace". It turns out "strace" is too low-level.

    So, thanks for your post.

    --
    When someone tells me he/she is a Christian I check to see if I'm
    still in possession of my wallet.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Kaz Kylheku@21:1/5 to Kenny McCormack on Sat Jan 18 01:22:51 2025
    On 2025-01-17, Kenny McCormack <gazelle@shell.xmission.com> wrote:
    In article <vmeibp$33sam$1@news.xmission.com>,
    Kenny McCormack <gazelle@shell.xmission.com> wrote:
    Context is Linux (and only Linux).

    Over the years, I have written many "interposers" - that is, a shared >>library loaded with LD_PRELOAD that hooks some system or library call >>(e.g., "read"). The interposer usually ends up calling the "real" >>function, then doing something special either before or after the call.

    Also, wanted to state that the function I am trying to hook is "openat".

    "open" I can hook, but "openat" doesn't work. I think most programs just call "open", which eventually ends up calling "openat", so that (hooking "open") works. But some programs call "openat" directly, and that doesn't work.

    And, also, just to clarify, the whole point of this is to modify the
    behavior of a program without the bother of re-compiling it.

    In one installation where glibc is 2.31 from 2021, I get this:

    $ nm /lib64/libc.so.6 | grep '1444b0'
    00000000001444b0 t __GI___openat
    00000000001444b0 t __GI___openat64
    00000000001444b0 t __libc_openat64
    00000000001444b0 t __openat
    00000000001444b0 W openat
    00000000001444b0 t __openat64
    00000000001444b0 W openat64

    Where 1444b0 is the address I discovered by another grep, and
    then used it to see what all the aliases are. The openat and
    openat64 symbols are W (weak).

    If you do the same exercise, what do you see?

    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)