Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/auditwheel/policy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@

_HERE = Path(__file__).parent
_MUSL_POLICY_RE = re.compile(r"^musllinux_\d+_\d+$")
# Match a versioned-symbol tag like "GLIBC_2.17", "ZLIB_NG_2.0.0",
# "CXXABI_LDBL_1.3" or "OPENSSL_1_1_0" and capture the namespace
# (everything before the trailing "_<numeric version>"). The version
# tail is required to be numeric (with `.` or `_` separators) so
# tags whose suffix is a word like "GLIBC_PRIVATE" don't match;
# those fall back to the legacy partition-on-first-underscore split.
_SYMBOL_VERSION_TAG_RE = re.compile(r"^([A-Za-z][A-Za-z0-9_]*?)_(\d+(?:[._]\d+)*)$")

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -176,7 +183,16 @@ def policy_is_satisfied(
required_vers: dict[str, set[str]] = {}
for symbols in versioned_symbols.values():
for symbol in symbols:
sym_name, _, _ = symbol.partition("_")
# Bucket the tag by namespace. Tags with a trailing
# numeric version (the common case) use the regex so
# multi-underscore namespaces like ZLIB_NG, CXXABI_LDBL,
# or LIBSSL_1_1 stay distinct from ZLIB / CXXABI / LIBSSL
# rather than being merged with them. Tags without a
# numeric tail (e.g. "GLIBC_PRIVATE", or fully bare
# tokens with no underscore) fall back to splitting on
# the first underscore so existing behavior is kept.
m = _SYMBOL_VERSION_TAG_RE.match(symbol)
sym_name = m.group(1) if m else symbol.partition("_")[0]
required_vers.setdefault(sym_name, set()).add(symbol)
for p in self._policies[::-1]:
policy_sym_vers = {
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/test_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,30 @@ def test_policy_checks_glibc():
policy = policies.versioned_symbols_policy({"some_library.so": {"IAMALIBRARY"}})
assert policy == policies.highest
assert policies.linux < policies.lowest < policies.highest


def test_policy_does_not_conflate_zlib_ng_with_zlib():
# Regression for https://github.com/pypa/auditwheel/issues/613.
# zlib-ng tags every exported symbol with ZLIB_NG_2.0.0 / ZLIB_NG_2.1.0
# via its GNU symbol version script. Splitting these tags on the first
# underscore put them in the "ZLIB" namespace and made them fail every
# policy whose ZLIB allowlist did not contain ZLIB_NG_*. The bucketing
# must keep ZLIB_NG distinct from ZLIB so the (unmodelled) ZLIB_NG
# namespace is silently dropped from the policy check, the same way
# any other unrecognised namespace already is.
policies = WheelPolicies(libc=Libc.GLIBC, arch=Architecture.x86_64)

policy = policies.versioned_symbols_policy(
{"libz-ng.so.2": {"ZLIB_NG_2.0.0", "ZLIB_NG_2.1.0"}},
)
assert policy == policies.highest

# And mixing libz-ng tags with stock-zlib tags must not poison the
# ZLIB bucket -- the ZLIB_1.2.0 check still needs to pass on its own.
policy = policies.versioned_symbols_policy(
{
"libz.so.1": {"ZLIB_1.2.0"},
"libz-ng.so.2": {"ZLIB_NG_2.0.0"},
},
)
assert policy > policies.linux
Loading