Skip to content

Commit 44cfc61

Browse files
authored
Check for a branded ELF note when OS/ABI is NONE (#1344)
Currently the auditor is only checking for the correct OS/ABI and producing a warning on FreeBSD when it doesn't match. This warning is incorrect on AArch64 though, as FreeBSD instead uses an ELF note to declare the OS/ABI. The change implemented here looks for such a note when the OS/ABI in the ELF header is NONE.
1 parent d2c1d6b commit 44cfc61

File tree

3 files changed

+177
-52
lines changed

3 files changed

+177
-52
lines changed

src/auditor/dynamic_linkage.jl

+35-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,31 @@
11
using ObjectFile.ELF
22
using Patchelf_jll: patchelf
33

4+
function os_from_elf_note(oh::ELFHandle)
5+
for section in Sections(oh)
6+
section_type(section) == ELF.SHT_NOTE || continue
7+
seek(oh, section_offset(section))
8+
name_length = read(oh, UInt32)
9+
iszero(name_length) && continue
10+
descriptor_length = read(oh, UInt32)
11+
note_type = read(oh, UInt32)
12+
name = String(read(oh, name_length - 1)) # skip trailing NUL
13+
if note_type == 1
14+
# Technically it's part of the Linux specification that any executable should
15+
# have an ELF note with type 1, name GNU, and descriptor length ≥4, but in
16+
# practice I haven't observed that consistently, especially on musl. So for
17+
# now, only bother checking FreeBSD, which uses an ELF note rather than OS/ABI
18+
# to identify itself on AArch64 and RISC-V.
19+
if name == "FreeBSD" && descriptor_length == 4
20+
return name
21+
end
22+
end
23+
end
24+
return nothing
25+
end
26+
27+
os_from_elf_note(::ObjectHandle) = nothing
28+
429
"""
530
platform_for_object(oh::ObjectHandle)
631
@@ -41,7 +66,9 @@ function platform_for_object(oh::ObjectHandle)
4166
end
4267
end
4368

44-
if oh.ei.osabi == ELF.ELFOSABI_LINUX || oh.ei.osabi == ELF.ELFOSABI_NONE
69+
if oh.ei.osabi == ELF.ELFOSABI_NONE
70+
return Platform(arch, os_from_elf_note(oh) == "FreeBSD" ? "freebsd" : "linux")
71+
elseif oh.ei.osabi == ELF.ELFOSABI_LINUX
4572
return Platform(arch, "linux")
4673
elseif oh.ei.osabi == ELF.ELFOSABI_FREEBSD
4774
return Platform(arch, "freebsd")
@@ -110,6 +137,13 @@ function is_for_platform(h::ObjectHandle, platform::AbstractPlatform)
110137
else
111138
error("Unknown OS ABI type $(typeof(platform))")
112139
end
140+
else
141+
# If no OSABI, check whether it has a matching ELF note
142+
if Sys.isfreebsd(platform)
143+
if os_from_elf_note(h) != "FreeBSD"
144+
return false
145+
end
146+
end
113147
end
114148
# Check that the ELF arch matches our own
115149
m = h.header.e_machine

src/auditor/extra_checks.jl

+15-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,19 @@ using Dates: DateTime, datetime2unix
55

66
function check_os_abi(oh::ObjectHandle, p::AbstractPlatform, rest...; verbose::Bool = false, kwargs...)
77
if Sys.isfreebsd(p)
8-
if oh.ei.osabi != ELF.ELFOSABI_FREEBSD
8+
# On AArch64 and RISC-V, FreeBSD uses an ELF note section to identify itself rather
9+
# than OS/ABI in the ELF header. In that case, the OS/ABI will be generic Unix (NONE).
10+
# See https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=252490 and
11+
# https://github.com/freebsd/freebsd-src/blob/d3c4b002d1fd54ac69c1714e208051867ee56dc4/lib/csu/common/crtbrand.S
12+
if oh.ei.osabi == ELF.ELFOSABI_NONE
13+
if os_from_elf_note(oh) != "FreeBSD"
14+
if verbose
15+
@warn("$(basename(path(oh))) does not have a FreeBSD-branded ELF note " *
16+
"and may be unrecognized or unusable on $p")
17+
end
18+
return false
19+
end
20+
elseif oh.ei.osabi != ELF.ELFOSABI_FREEBSD
921
# The dynamic loader should not have problems in this case, but the
1022
# linker may not appreciate. Let the user know about this.
1123
if verbose
@@ -17,7 +29,8 @@ function check_os_abi(oh::ObjectHandle, p::AbstractPlatform, rest...; verbose::B
1729
end
1830
return false
1931
end
20-
elseif call_abi(p) == "eabihf"
32+
end
33+
if call_abi(p) == "eabihf"
2134
# Make sure the object file has the hard-float ABI. See Table 4-2 of
2235
# "ELF for the ARM Architecture" document
2336
# (https://developer.arm.com/documentation/ihi0044/e/). Note: `0x000`

test/auditing.jl

+127-49
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using BinaryBuilder.Auditor
2-
using BinaryBuilder.Auditor: compatible_marchs, valid_library_path
2+
using BinaryBuilder.Auditor: check_os_abi, compatible_marchs, is_for_platform, valid_library_path
3+
using ObjectFile
34

45
# Tests for our auditing infrastructure
56

@@ -838,57 +839,134 @@ end
838839
end
839840

840841
@testset "Auditor - other checks" begin
841-
platform = Platform("armv7l", "linux"; call_abi = "eabihf", libc = "glibc")
842-
mktempdir() do build_path
843-
build_output_meta = @test_logs (:error, r"libsoft.so does not match the hard-float ABI") match_mode=:any begin
844-
autobuild(
845-
build_path,
846-
"hard_float_ABI",
847-
v"1.0.0",
848-
# No sources
849-
FileSource[],
850-
# Build a library which doesn't link to the standard library and
851-
# forces the soft-float ABI
852-
raw"""
853-
mkdir -p "${libdir}" "${bindir}"
854-
# This library has hard-float ABI
855-
echo 'int test() { return 0; }' | cc -shared -fPIC -o "${libdir}/libhard.${dlext}" -x c -
856-
# This library has soft-float ABI
857-
echo 'int _start() { return 0; }' | /opt/${target}/bin/${target}-gcc -nostdlib -shared -mfloat-abi=soft -o "${libdir}/libsoft.${dlext}" -x c -
858-
# hello_world built by Go doesn't specify any float ABI
859-
make -C /usr/share/testsuite/go/hello_world/
860-
cp "/tmp/testsuite/${target}/go/hello_world/hello_world" "${bindir}/hello_world"
861-
""",
862-
# Build for Linux armv7l hard-float
863-
[platform],
864-
# Ensure our library product is built
865-
[
866-
LibraryProduct("libhard", :libhard),
867-
LibraryProduct("libsoft", :libsoft),
868-
ExecutableProduct("hello_world", :hello_world),
869-
],
870-
# No dependencies
871-
Dependency[];
872-
compilers = [:c, :go],
873-
verbose = true,
874-
require_license = false
875-
)
842+
@testset "hard-float ABI" begin
843+
platform = Platform("armv7l", "linux"; call_abi = "eabihf", libc = "glibc")
844+
mktempdir() do build_path
845+
build_output_meta = @test_logs (:error, r"libsoft.so does not match the hard-float ABI") match_mode=:any begin
846+
autobuild(
847+
build_path,
848+
"hard_float_ABI",
849+
v"1.0.0",
850+
# No sources
851+
FileSource[],
852+
# Build a library which doesn't link to the standard library and
853+
# forces the soft-float ABI
854+
raw"""
855+
mkdir -p "${libdir}" "${bindir}"
856+
# This library has hard-float ABI
857+
echo 'int test() { return 0; }' | cc -shared -fPIC -o "${libdir}/libhard.${dlext}" -x c -
858+
# This library has soft-float ABI
859+
echo 'int _start() { return 0; }' | /opt/${target}/bin/${target}-gcc -nostdlib -shared -mfloat-abi=soft -o "${libdir}/libsoft.${dlext}" -x c -
860+
# hello_world built by Go doesn't specify any float ABI
861+
make -C /usr/share/testsuite/go/hello_world/
862+
cp "/tmp/testsuite/${target}/go/hello_world/hello_world" "${bindir}/hello_world"
863+
""",
864+
# Build for Linux armv7l hard-float
865+
[platform],
866+
# Ensure our library product is built
867+
[
868+
LibraryProduct("libhard", :libhard),
869+
LibraryProduct("libsoft", :libsoft),
870+
ExecutableProduct("hello_world", :hello_world),
871+
],
872+
# No dependencies
873+
Dependency[];
874+
compilers = [:c, :go],
875+
verbose = true,
876+
require_license = false
877+
)
878+
end
879+
880+
@test haskey(build_output_meta, platform)
881+
tarball_path, tarball_hash = build_output_meta[platform][1:2]
882+
@test isfile(tarball_path)
883+
884+
# Unpack it somewhere else
885+
@test verify(tarball_path, tarball_hash)
886+
testdir = joinpath(build_path, "testdir")
887+
mkdir(testdir)
888+
unpack(tarball_path, testdir)
889+
# Remove libsoft.so, we want to run audit only on the other products
890+
rm(joinpath(testdir, "lib", "libsoft.so"))
891+
# Make sure `hello_world` passes the float ABI check even if it doesn't
892+
# set `EF_ARM_ABI_FLOAT_HARD`.
893+
@test Auditor.audit(Prefix(testdir); platform=platform, require_license=false)
876894
end
895+
end
896+
@testset "OS/ABI: $platform" for platform in [Platform("x86_64", "freebsd"),
897+
Platform("aarch64", "freebsd")]
898+
mktempdir() do build_path
899+
build_output_meta = @test_logs (:warn, r"Skipping binary analysis of lib/lib(nonote|badosabi)\.so \(incorrect platform\)") match_mode=:any begin
900+
autobuild(
901+
build_path,
902+
"OSABI",
903+
v"4.2.0",
904+
# No sources
905+
FileSource[],
906+
# Build a library with a mismatched OS/ABI in the ELF header
907+
raw"""
908+
apk update
909+
apk add binutils
910+
mkdir -p "${libdir}"
911+
cd "${libdir}"
912+
echo 'int wrong() { return 0; }' | cc -shared -fPIC -o "libwrong.${dlext}" -x c -
913+
echo 'int right() { return 0; }' | cc -shared -fPIC -o "libright.${dlext}" -x c -
914+
cp "libwrong.${dlext}" "libnonote.${dlext}"
915+
# We only check for a branded ELF note when the OS/ABI is 0
916+
elfedit --output-osabi=none "libnonote.${dlext}"
917+
strip --remove-section=.note.tag "libnonote.${dlext}"
918+
mv "libwrong.${dlext}" "libbadosabi.${dlext}"
919+
# NetBSD runs anywhere, which implies that anything that runs is for NetBSD, right?
920+
elfedit --output-osabi=NetBSD "libbadosabi.${dlext}"
921+
""",
922+
[platform],
923+
# Ensure our library product is built
924+
[
925+
LibraryProduct("libbadosabi", :libbadosabi),
926+
LibraryProduct("libnonote", :libnonote),
927+
LibraryProduct("libright", :libright),
928+
],
929+
# No dependencies
930+
Dependency[];
931+
verbose=true,
932+
require_license=false,
933+
)
934+
end
877935

878-
@test haskey(build_output_meta, platform)
879-
tarball_path, tarball_hash = build_output_meta[platform][1:2]
880-
@test isfile(tarball_path)
936+
@test haskey(build_output_meta, platform)
937+
tarball_path, tarball_hash = build_output_meta[platform][1:2]
938+
@test isfile(tarball_path)
881939

882-
# Unpack it somewhere else
883-
@test verify(tarball_path, tarball_hash)
884-
testdir = joinpath(build_path, "testdir")
885-
mkdir(testdir)
886-
unpack(tarball_path, testdir)
887-
# Remove libsoft.so, we want to run audit only on the other products
888-
rm(joinpath(testdir, "lib", "libsoft.so"))
889-
# Make sure `hello_world` passes the float ABI check even if it doesn't
890-
# set `EF_ARM_ABI_FLOAT_HARD`.
891-
@test Auditor.audit(Prefix(testdir); platform=platform, require_license=false)
940+
# Unpack it somewhere else
941+
@test verify(tarball_path, tarball_hash)
942+
testdir = joinpath(build_path, "testdir")
943+
mkdir(testdir)
944+
unpack(tarball_path, testdir)
945+
readmeta(joinpath(testdir, "lib", "libright.so")) do ohs
946+
oh = only(ohs)
947+
@test is_for_platform(oh, platform)
948+
@test check_os_abi(oh, platform)
949+
end
950+
readmeta(joinpath(testdir, "lib", "libnonote.so")) do ohs
951+
oh = only(ohs)
952+
@test !is_for_platform(oh, platform)
953+
@test !check_os_abi(oh, platform)
954+
@test_logs((:warn, r"libnonote.so does not have a FreeBSD-branded ELF note"),
955+
match_mode=:any, check_os_abi(oh, platform; verbose=true))
956+
end
957+
readmeta(joinpath(testdir, "lib", "libbadosabi.so")) do ohs
958+
oh = only(ohs)
959+
@test !is_for_platform(oh, platform)
960+
@test !check_os_abi(oh, platform)
961+
@test_logs((:warn, r"libbadosabi.so has an ELF header OS/ABI value that is not set to FreeBSD"),
962+
match_mode=:any, check_os_abi(oh, platform; verbose=true))
963+
end
964+
# Only audit the library we didn't mess with in the recipe
965+
for bad in ("nonote", "badosabi")
966+
rm(joinpath(testdir, "lib", "lib$bad.so"))
967+
end
968+
@test Auditor.audit(Prefix(testdir); platform=platform, require_license=false)
969+
end
892970
end
893971
end
894972

0 commit comments

Comments
 (0)