Skip to content

Commit

Permalink
build: Ensure that binaries are run against their build-time ABI
Browse files Browse the repository at this point in the history
The /usr/bin/toolbox binary is not only used to interact with toolbox
containers and images from the host. It's also used as the entry point
of the containers by bind mounting the binary from the host into the
container. This means that the /usr/bin/toolbox binary on the host must
also work inside the container, even if they have different operating
systems.

In the past, this worked perfectly well with the POSIX shell
implementation because it got intepreted by whichever /bin/sh was
available. However, the Go implementation, can run into ABI
compatibility issues because binaries built on newer toolchains aren't
meant to be run against older runtimes.

The previous approach [1] of restricting the versions of the glibc
symbols that are linked against isn't actually supported by glibc, and
breaks if the early process start-up code changes. This is seen in
glibc-2.34, which is used by Fedora 35 onwards, where a new version of
the __libc_start_main symbol [2] was added as part of some security
hardening:
  $ objdump -T ./usr/bin/toolbox | grep GLIBC_2.34
  0000000000000000      DF *UND*	0000000000000000  GLIBC_2.34
    __libc_start_main
  0000000000000000      DF *UND*	0000000000000000  GLIBC_2.34
    pthread_detach
  0000000000000000      DF *UND*	0000000000000000  GLIBC_2.34
    pthread_create
  0000000000000000      DF *UND*	0000000000000000  GLIBC_2.34
    pthread_attr_getstacksize

This means that /usr/bin/toolbox binaries built against glibc-2.34 on
newer Fedoras fail to run against older glibcs in older Fedoras.

Another option is to make the host's runtime available inside the
toolbox container and ensure that the binary always runs against it.

Luckily, almost all supported containers have the host's /usr available
at /run/host/usr. This is exploited by embedding RPATHs or RUNPATHs to
/run/host/usr/lib and /run/host/usr/lib64 in the binary, and changing
the path of the dynamic linker (ie., PT_INTERP) to the one inside
/run/host.

Unfortunately, there can only be one PT_INTERP entry inside the
binary, so there must be a /run/host on the host too. Therefore, a
/run/host symbolic link is created on the host that points to the
host's /.

Based on ideas from Alexander Larsson and Ray Strode.

[1] Commit 6ad9c63
    #534

[2] glibc commit 035c012e32c11e84
    https://sourceware.org/git/?p=glibc.git;a=commit;h=035c012e32c11e84
    https://sourceware.org/bugzilla/show_bug.cgi?id=23323

#821
  • Loading branch information
debarshiray committed Oct 21, 2021
1 parent 452dc79 commit 6063eb2
Show file tree
Hide file tree
Showing 7 changed files with 18 additions and 63 deletions.
1 change: 1 addition & 0 deletions data/tmpfiles.d/toolbox.conf
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
d /run/media 0755 root root - -
L /run/host - - - - ../
8 changes: 2 additions & 6 deletions meson.build
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
project(
'toolbox',
'c',
version: '0.0.99.2',
license: 'ASL 2.0',
meson_version: '>= 0.42.0',
meson_version: '>= 0.53.0',
)

cc = meson.get_compiler('c')
add_project_arguments('-pthread', language: 'c')
add_project_link_arguments('-pthread', language: 'c')

go = find_program('go')
go_md2man = find_program('go-md2man')
patchelf = find_program('patchelf')
shellcheck = find_program('shellcheck', required: false)
skopeo = find_program('skopeo', required: false)

Expand Down
1 change: 1 addition & 0 deletions playbooks/setup-env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- golang-github-cpuguy83-md2man
- meson
- ninja-build
- patchelf
- podman
- skopeo
- systemd
Expand Down
17 changes: 14 additions & 3 deletions src/go-build-wrapper
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
#


if [ "$#" -ne 4 ]; then
if [ "$#" -ne 3 ]; then
echo "go-build-wrapper: wrong arguments" >&2
echo "Usage: go-build-wrapper [SOURCE DIR] [OUTPUT DIR] [VERSION] [libc-wrappers.a]" >&2
echo "Usage: go-build-wrapper [SOURCE DIR] [OUTPUT DIR] [VERSION]" >&2
exit 1
fi

Expand All @@ -27,5 +27,16 @@ if ! cd "$1"; then
exit 1
fi

go build -trimpath -ldflags "-extldflags '-Wl,--wrap,pthread_sigmask $4' -linkmode external -X github.com/containers/toolbox/pkg/version.currentVersion=$3" -o "$2/toolbox"
go build -trimpath -ldflags "-extldflags '-Wl,-rpath,/run/host/usr/lib -Wl,-rpath,/run/host/usr/lib64' -linkmode external -X github.com/containers/toolbox/pkg/version.currentVersion=$3" -o "$2/toolbox"

if ! interpreter=$(patchelf --print-interpreter "$2/toolbox"); then
echo "go-build-wrapper: failed to read PT_INTERP from $2/toolbox" >&2
exit 1
fi

if ! patchelf --set-interpreter "/run/host$interpreter" "$2/toolbox"; then
echo "go-build-wrapper: failed to change PT_INTERP of $2/toolbox to /run/host$interpreter" >&2
exit 1
fi

exit "$?"
42 changes: 0 additions & 42 deletions src/libc-wrappers/libc-wrappers.c

This file was deleted.

8 changes: 0 additions & 8 deletions src/libc-wrappers/meson.build

This file was deleted.

4 changes: 0 additions & 4 deletions src/meson.build
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
subdir('libc-wrappers')

go_build_wrapper_file = files('go-build-wrapper')
go_build_wrapper_program = find_program('go-build-wrapper')

Expand Down Expand Up @@ -28,9 +26,7 @@ custom_target(
meson.current_source_dir(),
meson.current_build_dir(),
meson.project_version(),
libc_wrappers.full_path(),
],
depends: libc_wrappers,
input: sources,
install: true,
install_dir: get_option('bindir'),
Expand Down

0 comments on commit 6063eb2

Please sign in to comment.