public inbox for libc-help@sourceware.org
 help / color / mirror / Atom feed
* Running old binaries that abuses setjmp/longjmp with recent glibc
@ 2020-01-17 21:30 Viktor Ostashevskyi
  2020-01-17 21:52 ` Florian Weimer
  0 siblings, 1 reply; 5+ messages in thread
From: Viktor Ostashevskyi @ 2020-01-17 21:30 UTC (permalink / raw)
  To: libc-help

[-- Attachment #1: Type: text/plain, Size: 1781 bytes --]

Hello,

Recently, I was playing was misc old i386 software: StarOffice 5.x,
Netscape Navigator 4.8, etc.
As you can assume it doesn't go very easy.

First problem was ABI breakage introduced by GCC somewhere near
version 4.4 regarding stack alignment
(https://gcc.gnu.org/bugzilla/show_bug.cgi?id=40838).
Fortunately, this can be workarounded by rebuilding 32-bit user-space
with '-mstackrealign'.

Second problem I encountered was more harder to solve and related to
glibc itself.
It turned out that Netscape 4.8 uses sigsetjmp/siglongjmp and alters
contents of jmp_buf, probably in order to implement some kind of
process-level multithreading.
Remnants of that are still visible in Mozillas NSPR library
(https://hg.mozilla.org/projects/nspr/file/tip/pr/include/md/_linux.h#l438)

This can't work since long ago as glibc mangles stack pointer and
program counter in jmp_buf (first mangling was added by Ulrich Drepper
in the end of 2005).

As search in the web didn't help with finding any workarounds, I tried
to create my own one: LD_PRELOAD with tiny library overriding
*setjmp/*longjmp. Overrides actually call glibc implementations but
demangle pointers after *setjmp and mangle them back before *longjmp.
(see attached code).

To my surprise this worked like charm and Netscape successfully started :)

Now I'm wonder whether glibc can't provide such library? It has
libBrokenLocale.so since at least 1996 (do we have ELF at 1996 at
all?), for fixing programs that could have used locales incorrectly in
some prehistoric time, but doesn't have anything for much newer
applications built against glibc 2.1 in much modern times.

Any ideas how this can be done in glibc?

-- 
З повагою, Осташевський Віктор

[-- Attachment #2: brokensetjmp.S --]
[-- Type: application/octet-stream, Size: 3314 bytes --]

/* Compile with: gcc -m32 -shared -o libBrokenSetJmp.so brokensetjmp.S */
/* Use with: LD_PRELOAD=libBrokenSetJmp.so ./application */

#define JB_SP 4
#define JB_PC 5
#define JMPBUF 4
#define SIGMSK JMPBUF + 4

#define POINTER_GUARD 0x18

#define PTR_MANGLE(reg)  xorl %gs:POINTER_GUARD, reg; \
                         roll $9, reg;

#define RTLD_NOW $-1

.section .rodata.str, "aMS", @progbits, 1
setjmp_name:
    .string "setjmp"
_setjmp_name:
    .string "_setjmp"
__sigsetjmp_name:
    .string "__sigsetjmp"

.section .text, "ax", @progbits
.global _setjmp, setjmp, __sigsetjmp, _longjmp, longjmp, siglongjmp
_setjmp:
    call __x86.get_pc_thunk.dx
    addl $_GLOBAL_OFFSET_TABLE_, %edx
    movl _setjmp_real@GOT(%edx), %edx
    /* Call real glibc implementation */
    pushl JMPBUF(%esp)
    call *(%edx)
    jmp .update_with_unmangled

setjmp:
    call __x86.get_pc_thunk.dx
    addl $_GLOBAL_OFFSET_TABLE_, %edx
    movl setjmp_real@GOT(%edx), %edx
    /* Call real glibc implementation */
    pushl JMPBUF(%esp)
    call *(%edx)
    jmp .update_with_unmangled

__sigsetjmp:
    call __x86.get_pc_thunk.dx
    addl $_GLOBAL_OFFSET_TABLE_, %edx
    movl __sigsetjmp_real@GOT(%edx), %edx
    /* Call real glibc implementation */
    pushl SIGMSK(%esp)
    pushl SIGMSK(%esp)
    call *(%edx)
    popl %edx /* Two args pushed, pop one back here */
    jmp .update_with_unmangled

.update_with_unmangled:
    /* Restore stack after call of real glibc implementation*/
    popl %edx
    /* After return jmp_buf will be filled with mangled SP and PC pointing to us */
    movl JMPBUF(%esp), %edx
    /* Put unmangled caller's SP */
    leal JMPBUF(%esp), %ecx
    movl %ecx, (JB_SP*4)(%edx)
    /* Put unamangled caller's PC */
    movl 0(%esp), %ecx
    movl %ecx, (JB_PC*4)(%edx)
    ret

_longjmp:
longjmp:
siglongjmp:
    /* Load jmp_buf */
    movl JMPBUF(%esp), %edx
    /* Mangle SP */
    movl (JB_SP*4)(%edx), %ecx
    PTR_MANGLE(%ecx)
    movl %ecx, (JB_SP*4)(%edx)
    /* Mangle PC */
    movl (JB_PC*4)(%edx), %ecx
    PTR_MANGLE(%ecx)
    movl %ecx, (JB_PC*4)(%edx)
    /* Jump to glibc implementation */
    call __x86.get_pc_thunk.dx
    addl $_GLOBAL_OFFSET_TABLE_, %edx
    movl %edx, %ebx /* No need to save/restore %ebx, as we're doing longjmp anyway */
    jmp __libc_siglongjmp@PLT

get_real_funcs:
    call __x86.get_pc_thunk.dx
    addl $_GLOBAL_OFFSET_TABLE_, %edx
    pushl %ebx
    movl %edx, %ebx

    leal setjmp_name@GOTOFF(%ebx), %eax
    pushl %eax
    pushl RTLD_NOW
    call dlsym@PLT
    movl %eax, setjmp_real@GOTOFF(%ebx)
    addl $0x8, %esp

    leal _setjmp_name@GOTOFF(%ebx), %eax
    pushl %eax
    pushl RTLD_NOW
    call dlsym@PLT
    movl %eax, _setjmp_real@GOTOFF(%ebx)
    addl $0x8, %esp

    leal __sigsetjmp_name@GOTOFF(%ebx), %eax
    pushl %eax
    pushl RTLD_NOW
    call dlsym@PLT
    movl %eax, __sigsetjmp_real@GOTOFF(%ebx)
    addl $0x8, %esp

    popl %ebx
    ret

.section .init_array, "aw"
.align 4
    .long get_real_funcs

.section .text.__x86.get_pc_thunk.dx, "axG", @progbits, __x86.get_pc_thunk.dx, comdat
.globl __x86.get_pc_thunk.dx
.hidden __x86.get_pc_thunk.dx
__x86.get_pc_thunk.dx:
    movl (%esp), %edx
    ret

.local setjmp_real, _setjmp_real, __sigsetjmp_real
.comm setjmp_real, 4, 4
.comm _setjmp_real, 4, 4
.comm __sigsetjmp_real, 4, 4

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Running old binaries that abuses setjmp/longjmp with recent glibc
  2020-01-17 21:30 Running old binaries that abuses setjmp/longjmp with recent glibc Viktor Ostashevskyi
@ 2020-01-17 21:52 ` Florian Weimer
  2020-01-17 22:05   ` Viktor Ostashevskyi
  0 siblings, 1 reply; 5+ messages in thread
From: Florian Weimer @ 2020-01-17 21:52 UTC (permalink / raw)
  To: Viktor Ostashevskyi; +Cc: libc-help

* Viktor Ostashevskyi:

> First problem was ABI breakage introduced by GCC somewhere near
> version 4.4 regarding stack alignment
> (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=40838).
> Fortunately, this can be workarounded by rebuilding 32-bit user-space
> with '-mstackrealign'.

The actual breakage was much earlier, but if you build glibc with recent
glibcs with SSE2 enabled, there is much more SSE2 usage, so this is more
noticeable.  -mstackrealign should fix this.

> Second problem I encountered was more harder to solve and related to
> glibc itself.
> It turned out that Netscape 4.8 uses sigsetjmp/siglongjmp and alters
> contents of jmp_buf, probably in order to implement some kind of
> process-level multithreading.
> Remnants of that are still visible in Mozillas NSPR library
> (https://hg.mozilla.org/projects/nspr/file/tip/pr/include/md/_linux.h#l438)
>
> This can't work since long ago as glibc mangles stack pointer and
> program counter in jmp_buf (first mangling was added by Ulrich Drepper
> in the end of 2005).

Interesting problem.  This kind of hardening is rather desirable, though.

> As search in the web didn't help with finding any workarounds, I tried
> to create my own one: LD_PRELOAD with tiny library overriding
> *setjmp/*longjmp. Overrides actually call glibc implementations but
> demangle pointers after *setjmp and mangle them back before *longjmp.
> (see attached code).
>
> To my surprise this worked like charm and Netscape successfully started :)
>
> Now I'm wonder whether glibc can't provide such library? It has
> libBrokenLocale.so since at least 1996 (do we have ELF at 1996 at
> all?), for fixing programs that could have used locales incorrectly in
> some prehistoric time, but doesn't have anything for much newer
> applications built against glibc 2.1 in much modern times.
>
> Any ideas how this can be done in glibc?

We could perhaps add a tunable that allows the user to disable the
hardening at process startup, e.g., instead of XORing with a random
value, always XOR with zero.  The cost for that would be quite small.

Thanks,
Florian

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Running old binaries that abuses setjmp/longjmp with recent glibc
  2020-01-17 21:52 ` Florian Weimer
@ 2020-01-17 22:05   ` Viktor Ostashevskyi
  2020-01-17 22:09     ` Florian Weimer
  0 siblings, 1 reply; 5+ messages in thread
From: Viktor Ostashevskyi @ 2020-01-17 22:05 UTC (permalink / raw)
  To: Florian Weimer; +Cc: libc-help

Hello,

пт, 17 січ. 2020 о 22:52 Florian Weimer <fweimer@redhat.com> пише:
> We could perhaps add a tunable that allows the user to disable the
> hardening at process startup, e.g., instead of XORing with a random
> value, always XOR with zero.  The cost for that would be quite small.

Wasn't it you who removed such tunable in
https://sourceware.org/git/?p=glibc.git;a=commit;h=a014cecd82b71b70a6a843e250e06b541ad524f7
? :)
And XOR'ing with 0 won't help, I've tried by reverting your commit
setting LD_POINTER_GUARD=1.
Current mangling on i386 applies shift with rotation after XORing. So
at least on i386 pointers were still mangled even with
LD_POINTER_GUARD=1.

-- 
З повагою, Осташевський Віктор

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Running old binaries that abuses setjmp/longjmp with recent glibc
  2020-01-17 22:05   ` Viktor Ostashevskyi
@ 2020-01-17 22:09     ` Florian Weimer
  2020-01-18 17:49       ` Viktor Ostashevskyi
  0 siblings, 1 reply; 5+ messages in thread
From: Florian Weimer @ 2020-01-17 22:09 UTC (permalink / raw)
  To: Viktor Ostashevskyi; +Cc: libc-help

* Viktor Ostashevskyi:

> Hello,
>
> пт, 17 січ. 2020 о 22:52 Florian Weimer <fweimer@redhat.com> пише:
>> We could perhaps add a tunable that allows the user to disable the
>> hardening at process startup, e.g., instead of XORing with a random
>> value, always XOR with zero.  The cost for that would be quite small.
>
> Wasn't it you who removed such tunable in
> https://sourceware.org/git/?p=glibc.git;a=commit;h=a014cecd82b71b70a6a843e250e06b541ad524f7
> ? :)
> And XOR'ing with 0 won't help, I've tried by reverting your commit
> setting LD_POINTER_GUARD=1.

Indeed, the rotate made it rather pointless.

> Current mangling on i386 applies shift with rotation after XORing. So
> at least on i386 pointers were still mangled even with
> LD_POINTER_GUARD=1.

Yes, we would have to remove the rotate from the setjmp/longjmp code.
There is no reason why it has to use the same obfuscation scheme than
the rest of the library.

Thanks,
Florian

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: Running old binaries that abuses setjmp/longjmp with recent glibc
  2020-01-17 22:09     ` Florian Weimer
@ 2020-01-18 17:49       ` Viktor Ostashevskyi
  0 siblings, 0 replies; 5+ messages in thread
From: Viktor Ostashevskyi @ 2020-01-18 17:49 UTC (permalink / raw)
  To: Florian Weimer, libc-help

[-- Attachment #1: Type: text/plain, Size: 1250 bytes --]

Hello,

пт, 17 січ. 2020 о 23:09 Florian Weimer <fweimer@redhat.com> пише:
>
> * Viktor Ostashevskyi:
>
> > Hello,
> >
> > пт, 17 січ. 2020 о 22:52 Florian Weimer <fweimer@redhat.com> пише:
> >> We could perhaps add a tunable that allows the user to disable the
> >> hardening at process startup, e.g., instead of XORing with a random
> >> value, always XOR with zero.  The cost for that would be quite small.
> >
> > Wasn't it you who removed such tunable in
> > https://sourceware.org/git/?p=glibc.git;a=commit;h=a014cecd82b71b70a6a843e250e06b541ad524f7
> > ? :)
> > And XOR'ing with 0 won't help, I've tried by reverting your commit
> > setting LD_POINTER_GUARD=1.
>
> Indeed, the rotate made it rather pointless.
>
> > Current mangling on i386 applies shift with rotation after XORing. So
> > at least on i386 pointers were still mangled even with
> > LD_POINTER_GUARD=1.
>
> Yes, we would have to remove the rotate from the setjmp/longjmp code.
> There is no reason why it has to use the same obfuscation scheme than
> the rest of the library.

Anything I can do to help? Source code of my LD_PRELOAD shared library
is attached.

-- 
З повагою, Осташевський Віктор

[-- Attachment #2: brokensetjmp.S --]
[-- Type: application/octet-stream, Size: 3314 bytes --]

/* Compile with: gcc -m32 -shared -o libBrokenSetJmp.so brokensetjmp.S */
/* Use with: LD_PRELOAD=libBrokenSetJmp.so ./application */

#define JB_SP 4
#define JB_PC 5
#define JMPBUF 4
#define SIGMSK JMPBUF + 4

#define POINTER_GUARD 0x18

#define PTR_MANGLE(reg)  xorl %gs:POINTER_GUARD, reg; \
                         roll $9, reg;

#define RTLD_NOW $-1

.section .rodata.str, "aMS", @progbits, 1
setjmp_name:
    .string "setjmp"
_setjmp_name:
    .string "_setjmp"
__sigsetjmp_name:
    .string "__sigsetjmp"

.section .text, "ax", @progbits
.global _setjmp, setjmp, __sigsetjmp, _longjmp, longjmp, siglongjmp
_setjmp:
    call __x86.get_pc_thunk.dx
    addl $_GLOBAL_OFFSET_TABLE_, %edx
    movl _setjmp_real@GOT(%edx), %edx
    /* Call real glibc implementation */
    pushl JMPBUF(%esp)
    call *(%edx)
    jmp .update_with_unmangled

setjmp:
    call __x86.get_pc_thunk.dx
    addl $_GLOBAL_OFFSET_TABLE_, %edx
    movl setjmp_real@GOT(%edx), %edx
    /* Call real glibc implementation */
    pushl JMPBUF(%esp)
    call *(%edx)
    jmp .update_with_unmangled

__sigsetjmp:
    call __x86.get_pc_thunk.dx
    addl $_GLOBAL_OFFSET_TABLE_, %edx
    movl __sigsetjmp_real@GOT(%edx), %edx
    /* Call real glibc implementation */
    pushl SIGMSK(%esp)
    pushl SIGMSK(%esp)
    call *(%edx)
    popl %edx /* Two args pushed, pop one back here */
    jmp .update_with_unmangled

.update_with_unmangled:
    /* Restore stack after call of real glibc implementation*/
    popl %edx
    /* After return jmp_buf will be filled with mangled SP and PC pointing to us */
    movl JMPBUF(%esp), %edx
    /* Put unmangled caller's SP */
    leal JMPBUF(%esp), %ecx
    movl %ecx, (JB_SP*4)(%edx)
    /* Put unamangled caller's PC */
    movl 0(%esp), %ecx
    movl %ecx, (JB_PC*4)(%edx)
    ret

_longjmp:
longjmp:
siglongjmp:
    /* Load jmp_buf */
    movl JMPBUF(%esp), %edx
    /* Mangle SP */
    movl (JB_SP*4)(%edx), %ecx
    PTR_MANGLE(%ecx)
    movl %ecx, (JB_SP*4)(%edx)
    /* Mangle PC */
    movl (JB_PC*4)(%edx), %ecx
    PTR_MANGLE(%ecx)
    movl %ecx, (JB_PC*4)(%edx)
    /* Jump to glibc implementation */
    call __x86.get_pc_thunk.dx
    addl $_GLOBAL_OFFSET_TABLE_, %edx
    movl %edx, %ebx /* No need to save/restore %ebx, as we're doing longjmp anyway */
    jmp __libc_siglongjmp@PLT

get_real_funcs:
    call __x86.get_pc_thunk.dx
    addl $_GLOBAL_OFFSET_TABLE_, %edx
    pushl %ebx
    movl %edx, %ebx

    leal setjmp_name@GOTOFF(%ebx), %eax
    pushl %eax
    pushl RTLD_NOW
    call dlsym@PLT
    movl %eax, setjmp_real@GOTOFF(%ebx)
    addl $0x8, %esp

    leal _setjmp_name@GOTOFF(%ebx), %eax
    pushl %eax
    pushl RTLD_NOW
    call dlsym@PLT
    movl %eax, _setjmp_real@GOTOFF(%ebx)
    addl $0x8, %esp

    leal __sigsetjmp_name@GOTOFF(%ebx), %eax
    pushl %eax
    pushl RTLD_NOW
    call dlsym@PLT
    movl %eax, __sigsetjmp_real@GOTOFF(%ebx)
    addl $0x8, %esp

    popl %ebx
    ret

.section .init_array, "aw"
.align 4
    .long get_real_funcs

.section .text.__x86.get_pc_thunk.dx, "axG", @progbits, __x86.get_pc_thunk.dx, comdat
.globl __x86.get_pc_thunk.dx
.hidden __x86.get_pc_thunk.dx
__x86.get_pc_thunk.dx:
    movl (%esp), %edx
    ret

.local setjmp_real, _setjmp_real, __sigsetjmp_real
.comm setjmp_real, 4, 4
.comm _setjmp_real, 4, 4
.comm __sigsetjmp_real, 4, 4

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2020-01-18 17:49 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-01-17 21:30 Running old binaries that abuses setjmp/longjmp with recent glibc Viktor Ostashevskyi
2020-01-17 21:52 ` Florian Weimer
2020-01-17 22:05   ` Viktor Ostashevskyi
2020-01-17 22:09     ` Florian Weimer
2020-01-18 17:49       ` Viktor Ostashevskyi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).