linkage_checker: replace Fiddle.dlopen with libSystem call

`dlopen`ing a library executes potentially untrusted code (e.g. if the
library has initialisers). We can avoid the `dlopen` call by asking
`libSystem` directly about whether a library can be found in the shared
cache.

Of course, the `dlopen` happens after a `ENOENT`, so the attack surface here
is relatively small. But relying on this still exposes us to a potential
TOCTOU[^1] bug. Let's avoid it entirely by skipping `dlopen` altogether.

Also: add RBI for `Fiddle` constants used in `linkage_checker`

Upstream don't have these definitions yet, so I've added an RBI for them
in the meantime.

[^1]: https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use
This commit is contained in:
Carlo Cabrera 2024-08-04 05:54:50 +08:00
parent 3994768349
commit d77c0392ef
No known key found for this signature in database
GPG Key ID: C74D447FC549A1D0
2 changed files with 28 additions and 7 deletions

View File

@ -160,9 +160,9 @@ class LinkageChecker
if (dep = dylib_to_dep(dylib))
@broken_deps[dep] |= [dylib]
elsif system_libraries_exist_in_cache? && dylib_found_via_dlopen(dylib)
elsif system_libraries_exist_in_cache? && dylib_found_in_shared_cache?(dylib)
# If we cannot associate the dylib with a dependency, then it may be a system library.
# If dlopen finds the dylib, then the linkage is not broken.
# Check the dylib shared cache for the library to verify this.
@system_dylibs << dylib
elsif !system_framework?(dylib) && !broken_dylibs_allowed?(file.to_s)
@broken_dylibs << dylib
@ -195,11 +195,18 @@ class LinkageChecker
end
alias generic_system_libraries_exist_in_cache? system_libraries_exist_in_cache?
def dylib_found_via_dlopen(dylib)
Fiddle.dlopen(dylib).close
true
rescue Fiddle::DLError
false
def dylib_found_in_shared_cache?(dylib)
@dyld_shared_cache_contains_path ||= begin
libc = Fiddle.dlopen("/usr/lib/libSystem.B.dylib")
Fiddle::Function.new(
libc["_dyld_shared_cache_contains_path"],
[Fiddle::TYPE_CONST_STRING],
Fiddle::TYPE_BOOL,
)
end
@dyld_shared_cache_contains_path.call(dylib)
end
def check_formula_deps

View File

@ -2,3 +2,17 @@
# This file contains temporary definitions for fixes that have
# been submitted upstream to https://github.com/sorbet/sorbet.
# Missing constants we use in `linkage_checker.rb`
# https://github.com/sorbet/sorbet/pull/8215
module Fiddle
# [`TYPE_BOOL`](https://github.com/ruby/fiddle/blob/v1.1.2/ext/fiddle/fiddle.h#L129)
#
# C type - bool
TYPE_BOOL = T.let(T.unsafe(nil).freeze, Integer)
# [`TYPE_CONST_STRING`](https://github.com/ruby/fiddle/blob/v1.1.2/ext/fiddle/fiddle.h#L128)
#
# C type - char\*
TYPE_CONST_STRING = T.let(T.unsafe(nil).freeze, Integer)
end