Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vdso: switch from DT_HASH to DT_GNU_HASH (aarch64) #2570

Open
wants to merge 1 commit into
base: criu-dev
Choose a base branch
from

Conversation

adrianreber
Copy link
Member

@adrianreber adrianreber commented Jan 22, 2025

Trying to run latest CRIU on CentOS Stream 10 or Ubuntu 24.04 (aarch64) fails like this:

    # criu/criu check -v4
    [...]
    (00.096460) vdso: Parsing at ffffb2e2a000 ffffb2e2c000
    (00.096539) vdso: PT_LOAD p_vaddr: 0
    (00.096567) vdso: DT_STRTAB: 1d0
    (00.096592) vdso: DT_SYMTAB: 128
    (00.096616) vdso: DT_STRSZ: 8a
    (00.096640) vdso: DT_SYMENT: 18
    (00.096663) Error (criu/pie-util-vdso.c:193): vdso: Not all dynamic entries are present
    (00.096688) Error (criu/vdso.c:627): vdso: Failed to fill self vdso symtable
    (00.096713) Error (criu/kerndat.c:1906): kerndat_vdso_fill_symtable failed when initializing kerndat.
    (00.096812) Found mmap_min_addr 0x10000
    (00.096881) files stat: fs/nr_open 1073741816
    (00.096908) Error (criu/crtools.c:267): Could not initialize kernel features detection.

This seems to be related to the kernel (6.12.0-41.el10.aarch64). The Ubuntu user-space is running in a container on the same kernel.

Looking at the kernel this seems to be related to:

    commit 48f6430505c0b0498ee9020ce3cf9558b1caaaeb
    Author: Fangrui Song <[email protected]>
    Date:   Thu Jul 18 10:34:23 2024 -0700

        arm64/vdso: Remove --hash-style=sysv

        glibc added support for .gnu.hash in 2006 and .hash has been obsoleted
        for more than one decade in many Linux distributions.  Using
        --hash-style=sysv might imply unaddressed issues and confuse readers.

        Just drop the option and rely on the linker default, which is likely
        "both", or "gnu" when the distribution really wants to eliminate sysv
        hash overhead.

        Similar to commit 6b7e26547fad ("x86/vdso: Emit a GNU hash").

The commit basically does:

    -ldflags-y := -shared -soname=linux-vdso.so.1 --hash-style=sysv \
    +ldflags-y := -shared -soname=linux-vdso.so.1 \

Which results in only a GNU hash being added to the ELF header. This change has been merged with 6.11.

Looking at the referenced x86 commit:

    commit 6b7e26547fad7ace3dcb27a5babd2317fb9d1e12
    Author: Andy Lutomirski <[email protected]>
    Date:   Thu Aug 6 14:45:45 2015 -0700

        x86/vdso: Emit a GNU hash

        Some dynamic loaders may be slightly faster if a GNU hash is
        available.  Strangely, this seems to have no effect at all on
        the vdso size.

        This is unlikely to have any measurable effect on the time it
        takes to resolve vdso symbols (since there are so few of them).
        In some contexts, it can be a win for a different reason: if
        every DSO has a GNU hash section, then libc can avoid
        calculating SysV hashes at all.  Both musl and glibc appear to
        have this optimization.

        It's plausible that this breaks some ancient glibc version.  If
        so, then, depending on what glibc versions break, we could
        either require COMPAT_VDSO for them or consider reverting.

Which is also a really simple change:

    -VDSO_LDFLAGS = -fPIC -shared $(call cc-ldoption, -Wl$(comma)--hash-style=sysv) \
    +VDSO_LDFLAGS = -fPIC -shared $(call cc-ldoption, -Wl$(comma)--hash-style=both) \

The big difference here is that for x86 both hash sections are generated. For aarch64 on the newer GNU hash is generated. That is why we only see this error on kernel >= 6.11 and aarch64.

Changing from DT_HASH to DT_GNU_HASH seems to work on aarch64. The test suite runs without any errors.

Unfortunately I am not aware of all implication of this change and if a successful test suite run means that it still works.

Looking at the kernel I see following hash styles for the VDSO:

aarch64: not specified (only GNU hash style)
arm: --hash-style=sysv
loongarch: --hash-style=sysv
mips: --hash-style=sysv
powerpc: --hash-style=both
riscv: --hash-style=both
s390: --hash-style=both
x86: --hash-style=both

@adrianreber
Copy link
Member Author

@avagin any comments from your side. This looks like it should be aarch64 only change. Posting it for feedback.

@0x7f454c46 any comments from your side. The VDSO code seems to be mostly from you.

@adrianreber
Copy link
Member Author

Hmm, it completely fails on older aarch64 kernels. Because they do not have GNU hash style section. Only the sysv hash style section. So we need to do runtime detection.

@adrianreber
Copy link
Member Author

Changed the PR to only look for DT_GNU_HASH on aarch64 if DT_HASH is not found.

@0x7f454c46
Copy link
Member

I'd say, you also need to differ them later on symbols parsing. Currently CRIU's elf_hash() is essentially Glibc's

static uint32_t
__attribute__ ((unused))
__simple_dl_elf_hash (const char *name_arg)
{
  unsigned long int hash = 0;
  for (unsigned char c = *name_arg; c != '\0'; c = *(++name_arg))
    {
      unsigned long int hi;
      hash = (hash << 4) + c;
      hi = hash & 0xf0000000;
      hash ^= hi >> 24;
      hash &= 0x0fffffff;
    }
  return hash;
}

And with this update, the new hash should be:

static uint32_t
__attribute__ ((unused))
__simple_dl_new_hash (const char *s)
{
  uint32_t h = 5381;
  for (unsigned char c = *s; c != '\0'; c = *++s)
    h = h * 33 + c;
  return h;
}

(both are un-optimized simplistic version from Glibc).

Maybe with small amount of vdso symbols it could work by luck, but yet probably look at check/dump logs to verify that kernel-provided symbols can be found after the patch.

@0x7f454c46
Copy link
Member

Also, thanks for this update! :-)

@adrianreber
Copy link
Member Author

adrianreber commented Jan 24, 2025

@0x7f454c46 Thanks for the review and thanks for the hash function. I added that hash function. Does the latest change look correct to you?

This is the vdso log output on the aarch64 test system I have:

(00.089310) pie: 4: vdso: Remap rt-vdso 0xffff981a6000 -> 0x2b000
(00.089378) pie: 4: vdso: Remap rt-vvar 0xffff981a4000 -> 0x29000
(00.089417) pie: 4: vdso: No gettimeofday() on rt-vdso
(00.092429) pie: 4: vdso: No gettimeofday() on rt-vdso
(00.093507) pie: 4: vdso: Parsing at 0xffff82d0a000 0xffff82d0c000
(00.093521) pie: 4: vdso: PT_LOAD p_vaddr: 0x0
(00.093533) pie: 4: vdso: DT_GNU_HASH: 0xe8
(00.093544) pie: 4: vdso: DT_STRTAB: 0x1d0
(00.093556) pie: 4: vdso: DT_SYMTAB: 0x128
(00.093567) pie: 4: vdso: DT_STRSZ: 0x8a
(00.093579) pie: 4: vdso: DT_SYMENT: 0x18
(00.093590) pie: 4: vdso: nbucket 0x3 nchain 0x1 bucket 0xffff82d0a0f0 chain 0xffff82d0a0fc
(00.093604) pie: 4: vdso: image [vdso] 0xffff82d0a000-0xffff82d0c000 [vvar] 0xffff82d08000-0xffff82d0a000
(00.093629) pie: 4: vdso: Runtime vdso/vvar matches dumpee, remap inplace
(00.093669) pie: 4: vdso: Remap rt-vdso 0x2b000 -> 0xffff82d0a000
(00.093697) pie: 4: vdso: Remap rt-vvar 0x29000 -> 0xffff82d08000
(00.093724) pie: 4: vdso: No gettimeofday() on rt-vdso
(00.096090) pie: 1: vdso: Remap rt-vdso 0xffff981a6000 -> 0x2b000
(00.096127) pie: 1: vdso: Remap rt-vvar 0xffff981a4000 -> 0x29000
(00.096164) pie: 1: vdso: No gettimeofday() on rt-vdso
(00.099803) pie: 1: vdso: No gettimeofday() on rt-vdso
(00.100824) pie: 1: vdso: Parsing at 0xffff82d0a000 0xffff82d0c000
(00.100837) pie: 1: vdso: PT_LOAD p_vaddr: 0x0
(00.100850) pie: 1: vdso: DT_GNU_HASH: 0xe8
(00.100862) pie: 1: vdso: DT_STRTAB: 0x1d0
(00.100874) pie: 1: vdso: DT_SYMTAB: 0x128
(00.100885) pie: 1: vdso: DT_STRSZ: 0x8a
(00.100897) pie: 1: vdso: DT_SYMENT: 0x18
(00.100909) pie: 1: vdso: nbucket 0x3 nchain 0x1 bucket 0xffff82d0a0f0 chain 0xffff82d0a0fc
(00.100921) pie: 1: vdso: image [vdso] 0xffff82d0a000-0xffff82d0c000 [vvar] 0xffff82d08000-0xffff82d0a000
(00.100934) pie: 1: vdso: Runtime vdso/vvar matches dumpee, remap inplace
(00.100974) pie: 1: vdso: Remap rt-vdso 0x2b000 -> 0xffff82d0a000
(00.101006) pie: 1: vdso: Remap rt-vvar 0x29000 -> 0xffff82d08000
(00.101032) pie: 1: vdso: No gettimeofday() on rt-vdso

@0x7f454c46
Copy link
Member

Heh, it seems my linker by default produces both:

[~/src/linux-master]$ uname -a
Linux arm64-nix-master 6.13.0-09030-g6d61a53dd6f5 #22 SMP PREEMPT Tue Jan 28 10:00:42 GMT 2025 aarch64 GNU/Linux

[~/src/linux-master]$ readelf --dynamic arch/arm64/kernel/vdso/vdso.so

Dynamic section at offset 0xec0 contains 15 entries:
  Tag        Type                         Name/Value
 0x000000000000000e (SONAME)             Library soname: [linux-vdso.so.1]
 0x0000000000000010 (SYMBOLIC)           0x0
 0x000000000000001d (RUNPATH)            Library runpath: [/nix/store/drckkfr552pyam4lylga8dzsfpbh4w6m-shell/lib]
 0x0000000000000004 (HASH)               0xe8
 0x000000006ffffef5 (GNU_HASH)           0x118
 0x0000000000000005 (STRTAB)             0x200
[...]

So, albeit I like the long comment you've written, but it seems slightly incorrect about only DT_GNU_HASH on v6.11+.

With some debug (probably can be a patch) before:

(00.101647) vdso: Parsing at ffff97967000 ffff97969000
(00.101662) vdso: PT_LOAD p_vaddr: 0
(00.101665) vdso: DT_HASH: e8
(00.101669) vdso: DT_STRTAB: 200
(00.101672) vdso: DT_SYMTAB: 158
(00.101674) vdso: DT_STRSZ: c0
(00.101677) vdso: DT_SYMENT: 18
(00.101680) vdso: hash ffff979670e8
(00.101683) vdso: nbucket 3 nchain 7 bucket ffff979670f0 chain ffff979670fc
(00.101687) vdso: symbol __kernel_clock_getres at offset 870
(00.101690) vdso: symbol __kernel_clock_gettime at offset 360
(00.101693) vdso: symbol __kernel_gettimeofday at offset 668
(00.101695) vdso: symbol __kernel_rt_sigreturn at offset 948
(00.101698) vdso: rt [vdso] ffff97967000-ffff97969000 [vvar] ffff97965000-ffff97967000

when I ifdef-out DT_HASH [on the top of your patch], the debug prints for vdso symbols do not appear, hmm:

(00.108716) vdso: Parsing at ffffa6f80000 ffffa6f82000
(00.108737) vdso: PT_LOAD p_vaddr: 0
(00.108741) vdso: DT_GNU_HASH: 118
(00.108748) vdso: DT_STRTAB: 200
(00.108751) vdso: DT_SYMTAB: 158
(00.108754) vdso: DT_STRSZ: c0
(00.108757) vdso: DT_SYMENT: 18
(00.108760) vdso: hash ffffa6f80118
(00.108763) vdso: nbucket 3 nchain 1 bucket ffffa6f80120 chain ffffa6f8012c
(00.108766) vdso: rt [vdso] ffffa6f80000-ffffa6f82000 [vvar] ffffa6f7e000-ffffa6f80000

So, I spent some time on this...
Here is what I got: 50a6d3f
It parses the elf with either sysv-hash or gnu-hash and creates the resulting vdso symtable. Please, feel free to reuse it for this PR.
[don't forget to do rm /run/criu.kdat between the tests as the symtable on the running machine is cached].

@0x7f454c46
Copy link
Member

With some debug (probably can be a patch) before:

The pritings for vdso functions' offsets went into the commit above, so this line was written when there wasn't a patch yet.

@adrianreber
Copy link
Member Author

Thanks for reworking the PR, that also works on my test system. Let's update this PR with your version.

criu/pie/util-vdso.c Dismissed Show dismissed Hide dismissed
@adrianreber
Copy link
Member Author

Interesting CI result:

 ==51==ERROR: AddressSanitizer: global-buffer-overflow on address 0x0000006aa615 at pc 0x7f54f4f61576 bp 0x7ffecbf34db0 sp 0x7ffecbf34570
READ of size 31 at 0x0000006aa615 thread T0
    #0 0x7f54f4f61575 in memcpy (/lib64/libasan.so.8+0xc0575) (BuildId: 0505b45e5a5d9a6c8ecb1d529aaaf13cd21fbe4e)
    #1 0x51018d in parse_elf_symbols criu/pie-util-vdso.c:381
    #2 0x51018d in vdso_fill_symtable criu/pie-util-vdso.c:434
    #3 0x593e39 in vdso_fill_self_symtable criu/vdso.c:412
    #4 0x593e39 in kerndat_vdso_fill_symtable criu/vdso.c:626
    #5 0x4ab08c in kerndat_init criu/kerndat.c:1905
    #6 0x404b7e in main criu/crtools.c:266
    #7 0x7f54f493e247 in __libc_start_call_main (/lib64/libc.so.6+0x3247) (BuildId: 515c33a35f41020661fea8ac4eb995e26ccd6b00)
    #8 0x7f54f493e30a in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x330a) (BuildId: 515c33a35f41020661fea8ac4eb995e26ccd6b00)
    #9 0x406b44 in _start (/criu/criu/criu+0x406b44) (BuildId: 35632171dcca76a5eec6be658232a206ceb2fe97)

@adrianreber
Copy link
Member Author

Pushed a version that should no longer have the buffer overflow.

Copy link
Member

@0x7f454c46 0x7f454c46 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks Adrian!

criu/pie/util-vdso.c Outdated Show resolved Hide resolved
@adrianreber adrianreber force-pushed the 2025-01-22-gnu-hash branch 2 times, most recently from 351973d to 8783bd0 Compare January 30, 2025 07:19
Trying to run latest CRIU on CentOS Stream 10 or Ubuntu 24.04 (aarch64)
fails like this:

    # criu/criu check -v4
    [...]
    (00.096460) vdso: Parsing at ffffb2e2a000 ffffb2e2c000
    (00.096539) vdso: PT_LOAD p_vaddr: 0
    (00.096567) vdso: DT_STRTAB: 1d0
    (00.096592) vdso: DT_SYMTAB: 128
    (00.096616) vdso: DT_STRSZ: 8a
    (00.096640) vdso: DT_SYMENT: 18
    (00.096663) Error (criu/pie-util-vdso.c:193): vdso: Not all dynamic entries are present
    (00.096688) Error (criu/vdso.c:627): vdso: Failed to fill self vdso symtable
    (00.096713) Error (criu/kerndat.c:1906): kerndat_vdso_fill_symtable failed when initializing kerndat.
    (00.096812) Found mmap_min_addr 0x10000
    (00.096881) files stat: fs/nr_open 1073741816
    (00.096908) Error (criu/crtools.c:267): Could not initialize kernel features detection.

This seems to be related to the kernel (6.12.0-41.el10.aarch64). The
Ubuntu user-space is running in a container on the same kernel.

Looking at the kernel this seems to be related to:

    commit 48f6430505c0b0498ee9020ce3cf9558b1caaaeb
    Author: Fangrui Song <[email protected]>
    Date:   Thu Jul 18 10:34:23 2024 -0700

        arm64/vdso: Remove --hash-style=sysv

        glibc added support for .gnu.hash in 2006 and .hash has been obsoleted
        for more than one decade in many Linux distributions.  Using
        --hash-style=sysv might imply unaddressed issues and confuse readers.

        Just drop the option and rely on the linker default, which is likely
        "both", or "gnu" when the distribution really wants to eliminate sysv
        hash overhead.

        Similar to commit 6b7e26547fad ("x86/vdso: Emit a GNU hash").

The commit basically does:

    -ldflags-y := -shared -soname=linux-vdso.so.1 --hash-style=sysv \
    +ldflags-y := -shared -soname=linux-vdso.so.1 \

Which results in only a GNU hash being added to the ELF header. This
change has been merged with 6.11.

Looking at the referenced x86 commit:

    commit 6b7e26547fad7ace3dcb27a5babd2317fb9d1e12
    Author: Andy Lutomirski <[email protected]>
    Date:   Thu Aug 6 14:45:45 2015 -0700

        x86/vdso: Emit a GNU hash

        Some dynamic loaders may be slightly faster if a GNU hash is
        available.  Strangely, this seems to have no effect at all on
        the vdso size.

        This is unlikely to have any measurable effect on the time it
        takes to resolve vdso symbols (since there are so few of them).
        In some contexts, it can be a win for a different reason: if
        every DSO has a GNU hash section, then libc can avoid
        calculating SysV hashes at all.  Both musl and glibc appear to
        have this optimization.

        It's plausible that this breaks some ancient glibc version.  If
        so, then, depending on what glibc versions break, we could
        either require COMPAT_VDSO for them or consider reverting.

Which is also a really simple change:

    -VDSO_LDFLAGS = -fPIC -shared $(call cc-ldoption, -Wl$(comma)--hash-style=sysv) \
    +VDSO_LDFLAGS = -fPIC -shared $(call cc-ldoption, -Wl$(comma)--hash-style=both) \

The big difference here is that for x86 both hash sections are
generated. For aarch64 only the newer GNU hash is generated. That is why
we only see this error on kernel >= 6.11 and aarch64.

Changing from DT_HASH to DT_GNU_HASH seems to work on aarch64.  The test
suite runs without any errors.

Unfortunately I am not aware of all implication of this change and if a
successful test suite run means that it still works.

Looking at the kernel I see following hash styles for the VDSO:

aarch64: not specified (only GNU hash style)
arm: --hash-style=sysv
loongarch: --hash-style=sysv
mips: --hash-style=sysv
powerpc: --hash-style=both
riscv: --hash-style=both
s390: --hash-style=both
x86: --hash-style=both

Only aarch64 on kernels >= 6.11 is a problem right now, because all
other platforms provide the old style hashing.

Signed-off-by: Adrian Reber <[email protected]>
Co-developed-by: Dmitry Safonov <[email protected]>
Co-authored-by: Dmitry Safonov <[email protected]>
Signed-off-by: Dmitry Safonov <[email protected]>
@adrianreber
Copy link
Member Author

adrianreber commented Jan 30, 2025

One of the tests is failing with:

criu/pie-util-vdso-elf32.c: In function ‘parse_elf_symbols’:
criu/pie-util-vdso-elf32.c:388:24: error: ‘sym_off’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
  388 |                 addr = elf_symbol_lookup(mem, size, symbol, symbol_hash,
      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  389 |                                 sym_off, dynsymbol_names, dyn_symtab, load,
      |                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  390 |                                 nbucket, nchain, bucket, chain,
      |                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  391 |                                 vdso_symbol_length, use_gnu_hash);
      |                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
criu/pie-util-vdso-elf32.c:388:24: error: ‘chain’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
criu/pie-util-vdso-elf32.c:388:24: error: ‘nchain’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
criu/pie-util-vdso.c: In function ‘parse_elf_symbols’:
criu/pie-util-vdso.c:388:24: error: ‘sym_off’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
  388 |                 addr = elf_symbol_lookup(mem, size, symbol, symbol_hash,
      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  389 |                                 sym_off, dynsymbol_names, dyn_symtab, load,
      |                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  390 |                                 nbucket, nchain, bucket, chain,
      |                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  391 |                                 vdso_symbol_length, use_gnu_hash);
      |                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gcc -c --coverage -fno-exceptions -fno-inline -fprofile-update=atomic -O2 -g -Wall -Wformat-security -Wdeclaration-after-statement -Wstrict-prototypes -Wno-dangling-pointer -Wno-unknown-warning-option -Werror -DCONFIG_X86_64 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -D_GNU_SOURCE -DCONFIG_HAS_LIBBSD -DCONFIG_HAS_SELINUX -DCONFIG_GNUTLS -DCONFIG_COMPAT -iquote include/ -DCONFIG_HAS_LIBBSD -DCONFIG_HAS_SELINUX -DCONFIG_GNUTLS -DCONFIG_COMPAT -I ./compel/include/uapi -fno-strict-aliasing -iquote criu/include -iquote include -iquote images -iquote criu/arch/x86/include -iquote . -I/usr/include/libnl3 -DSYSCONFDIR='"/etc"' -DGLOBAL_CONFIG_DIR='"/etc/criu/"' -DDEFAULT_CONFIG_FILENAME='"default.conf"' -DUSER_CONFIG_DIR='".criu/"' criu/plugin.c -o criu/plugin.o
criu/pie-util-vdso.c:388:24: error: ‘chain’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
criu/pie-util-vdso.c:388:24: error: ‘nchain’ may be used uninitialized in this function [-Werror=maybe-uninitialized]

The compiler cannot figure out, that those variables are not always needed. I think the code is correct. I am still initializing those variables to make the compiler happy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants