Skip to content

Commit 77db695

Browse files
committed
FIX: PR #744 review round 2 (Sumit) — probe/audit hardening (4 Low)
- tls_connect_probe.py: _split_top_level now models a SINGLE-LEVEL braced value matching the production parser (connection_string_parser.py::_parse_braced_value) -- an inner `{` is a literal (not a nested open) and a `{` is only a brace-open at a value's START, so `Pwd={a{b};Encrypt=no` splits correctly and force_tls no longer emits a duplicate `encrypt` that mssql_python.connect would reject. (The production parser cannot be imported here: it pulls in mssql_python -> the native ddbc_bindings extension, which this standalone probe must stay importable / unit-testable without.) - audit_bundled_binaries.py::_openssl_range_ok: the upper bound is valid only as an EXCLUSIVE `<` at numeric release 4.0(.0...) -- `<4`, `<4.0`, `<4.0.0`, `<4.0a0` pass; `<=4`, `<4.1`, `<4.0.1` (each admitting some openssl 4.x) now correctly FAIL. - audit_bundled_binaries.py: an unknown `linux-*` subdir with no _SUBDIR_MACHINE mapping now FAILS CLOSED instead of proceeding with expected_machine=None and silently skipping the ELF architecture gate. - _conda_pkg.py::iter_payload_members: a `.conda` missing its pkg-*.tar.zst now RAISES (was a bare `return` -> silent empty iteration); both audit scripts convert that to a violation. The Medium (the job-level TLS secret never reaching the posix `bash:` step) is already resolved: the live Encrypt=yes gate was removed from the build pipeline entirely in the previous commit (it moves to the release pipeline, where the secret is always present so the gate is unconditional). +7 unit tests (119 pass). black + flake8 clean; mypy clean on the scripts.
1 parent 2f26bd6 commit 77db695

7 files changed

Lines changed: 157 additions & 46 deletions

‎conda/tls_connect_probe.py‎

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -90,37 +90,53 @@ def describe(exc):
9090

9191

9292
def _split_top_level(conn):
93-
"""Split an ODBC connection string on TOP-LEVEL ``;`` only.
94-
95-
An ODBC value wrapped in ``{...}`` may itself contain ``;`` (MS-ODBCSTR), so a
96-
naive ``split(';')`` would shred braced values. Track brace depth and break only
97-
at depth 0. Inside a braced value ``}}`` is an escaped literal ``}`` (MS-ODBCSTR),
98-
NOT a close -- consume both and keep the depth so the value is not split early.
93+
"""Split an ODBC connection string on TOP-LEVEL ``;`` only, matching the production
94+
parser's grammar (mssql_python/connection_string_parser.py, ``_parse_braced_value``).
95+
96+
A value is BRACED only when ``{`` is the first non-space char right after its ``=``;
97+
inside a braced value everything is literal until a single closing ``}`` (with ``}}`` an
98+
escaped literal ``}``), and an inner ``{`` is NOT a nested open -- braced values are
99+
single-level. A ``;`` inside a braced value is part of the value, not a separator; a
100+
``{`` anywhere other than a value's start is a literal character.
101+
102+
(Reimplemented rather than importing the production parser: that module pulls in
103+
``mssql_python`` -> the native ``ddbc_bindings`` extension, which this standalone probe
104+
must stay importable / unit-testable without.)
99105
"""
100106
segments = []
101-
buf = ""
102-
depth = 0
103107
s = conn.strip()
108+
n = len(s)
104109
i = 0
105-
while i < len(s):
110+
seg_start = 0
111+
seen_eq = False # have we passed the '=' that starts this segment's value?
112+
while i < n:
106113
ch = s[i]
107-
if ch == "{":
108-
depth += 1
109-
buf += ch
110-
elif ch == "}":
111-
if depth > 0 and i + 1 < len(s) and s[i + 1] == "}":
112-
buf += "}}" # escaped literal '}' inside a braced value; not a close
113-
i += 2
114-
continue
115-
depth = max(0, depth - 1)
116-
buf += ch
117-
elif ch == ";" and depth == 0:
118-
segments.append(buf)
119-
buf = ""
120-
else:
121-
buf += ch
114+
if ch == ";":
115+
segments.append(s[seg_start:i])
116+
i += 1
117+
seg_start = i
118+
seen_eq = False
119+
continue
120+
if ch == "=" and not seen_eq:
121+
seen_eq = True
122+
i += 1
123+
# A braced value begins ONLY if '{' is the first non-space char after '='.
124+
j = i
125+
while j < n and s[j] in " \t":
126+
j += 1
127+
if j < n and s[j] == "{":
128+
i = j + 1 # past the opening '{'
129+
while i < n:
130+
if s[i] == "}":
131+
if i + 1 < n and s[i + 1] == "}":
132+
i += 2 # escaped literal '}' -- stay in the braced value
133+
continue
134+
i += 1 # single '}' closes the braced value
135+
break
136+
i += 1 # any other char (incl. an inner '{') is literal
137+
continue
122138
i += 1
123-
segments.append(buf)
139+
segments.append(s[seg_start:])
124140
return segments
125141

126142

‎eng/scripts/_conda_pkg.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def iter_payload_members(path: str):
4040
None,
4141
)
4242
if pkg_name is None:
43-
return
43+
raise ValueError(f"{path}: no pkg-*.tar.zst payload found in .conda archive")
4444
blob = zstd_decompress(zf.read(pkg_name))
4545
with tarfile.open(fileobj=io.BytesIO(blob)) as tf:
4646
for m in tf.getmembers():

‎eng/scripts/assert_pe_machine.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,16 @@ def audit_package(path: str) -> list[str]:
7676
print(f" SKIP (no Windows PE payload): {base_name} [subdir={subdir or '?'}]")
7777
return []
7878

79+
try:
80+
members = list(_iter_payload_members(path))
81+
except ValueError as exc: # malformed payload (e.g. .conda missing pkg-*.tar.zst)
82+
return [f"{base_name}: unreadable/malformed package payload ({exc})."]
83+
7984
errors: list[str] = []
8085
native_seen = 0
8186
binding_seen = 0
8287
driver_dll_seen = 0
83-
for name, data in _iter_payload_members(path):
88+
for name, data in members:
8489
if not name.lower().endswith(_NATIVE_SUFFIXES):
8590
continue
8691
native_seen += 1

‎eng/scripts/audit_bundled_binaries.py‎

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,14 @@ def _dep_names(depends) -> set:
229229

230230

231231
def _openssl_range_ok(constraint: str) -> bool:
232-
"""True iff an openssl spec pins the Driver-18 ABI range: a ``>=3`` lower AND a ``<4`` upper.
233-
234-
Parses each comma-separated clause as ``(operator, version)`` instead of substring-
235-
matching, so a loose spelling like ``>=3,<40`` (whose ``<40`` merely CONTAINS ``<4``)
236-
is correctly REJECTED, while conda's canonical alpha upper bound ``<4.0a0`` is accepted.
237-
A spec with no upper bound (bare ``>=3``) is rejected -- it would admit openssl 4.
232+
"""True iff an openssl spec pins the Driver-18 ABI range: a ``>=3`` lower AND an EXCLUSIVE
233+
upper that admits NO openssl 4.x.
234+
235+
Parses each comma-separated clause as ``(operator, version)``. The upper bound is valid
236+
ONLY as an exclusive ``<`` whose numeric release is ``4`` with trailing zeros -- ``<4``,
237+
``<4.0``, ``<4.0.0`` or a ``4.0`` pre-release like ``<4.0a0`` (conda's canonical bound).
238+
``<=4``, ``<4.1`` and ``<4.0.1`` each admit some 4.x build and are REJECTED, as is a spec
239+
with no upper bound (bare ``>=3``) and a loose ``<40`` (whose ``<40`` is not ``<4``).
238240
"""
239241
has_lower_3 = False
240242
has_upper_4 = False
@@ -243,10 +245,14 @@ def _openssl_range_ok(constraint: str) -> bool:
243245
if not m:
244246
continue
245247
op, ver = m.group(1), m.group(2)
246-
major = int(ver.split(".")[0])
247-
if op in (">=", ">", "==", "=") and major == 3:
248+
parts = [int(p) for p in ver.split(".") if p != ""]
249+
if not parts:
250+
continue
251+
if op in (">=", ">", "==", "=") and parts[0] == 3:
248252
has_lower_3 = True
249-
elif op in ("<", "<=") and major == 4:
253+
# Exclusive '<' only, at numeric release 4.0(.0...) -- a '4.0' pre-release such as
254+
# '<4.0a0' captures as '4.0' here, so it qualifies; '<=4', '<4.1', '<4.0.1' do not.
255+
elif op == "<" and parts[0] == 4 and all(p == 0 for p in parts[1:]):
250256
has_upper_4 = True
251257
return has_lower_3 and has_upper_4
252258

@@ -264,8 +270,16 @@ def audit_package(path: str) -> list[str]:
264270
print(f" SKIP (no Linux ELF payload): {base_name} [subdir={subdir or '?'}]")
265271
return []
266272

267-
# The conda subdir is the arch authority; every vendored ELF must match it.
273+
# The conda subdir is the arch authority; every vendored ELF must match it. A linux
274+
# subdir with no e_machine mapping (e.g. a future linux-ppc64le) FAILS CLOSED rather than
275+
# silently skipping the architecture gate this audit exists to enforce.
268276
expected_machine = _SUBDIR_MACHINE.get(subdir)
277+
if expected_machine is None:
278+
return [
279+
f"{base_name}: unrecognized Linux subdir '{subdir}' has no known ELF machine "
280+
f"mapping -- add it to _SUBDIR_MACHINE so the arch gate can enforce it "
281+
f"(refusing to skip the architecture check)."
282+
]
269283

270284
errors: list[str] = []
271285

@@ -297,7 +311,12 @@ def audit_package(path: str) -> list[str]:
297311
dirs_with_inst: set = set()
298312
vendored: list[str] = []
299313

300-
for name, data in _iter_payload_members(path):
314+
try:
315+
members = list(_iter_payload_members(path))
316+
except ValueError as exc: # malformed payload (e.g. .conda missing pkg-*.tar.zst)
317+
return [f"{base_name}: unreadable/malformed package payload ({exc})."]
318+
319+
for name, data in members:
301320
base = posixpath.basename(name)
302321
norm = "/" + name
303322
member_dir = posixpath.dirname(name)
@@ -323,14 +342,13 @@ def audit_package(path: str) -> list[str]:
323342
# Architecture gate: the ELF machine MUST match the package's conda subdir, so
324343
# an x86_64 driver mislabeled under a linux-aarch64 package (which the emulated
325344
# leg's best-effort runtime probe would not catch) fails here.
326-
if expected_machine is not None:
327-
mach = elf_machine(data)
328-
if mach != expected_machine:
329-
errors.append(
330-
f"{name}: ELF machine {mach} ({_MACHINE_NAME.get(mach, 'unknown')}) does "
331-
f"not match the '{subdir}' package arch {expected_machine} "
332-
f"({_MACHINE_NAME[expected_machine]}) -- wrong-arch/mislabeled driver."
333-
)
345+
mach = elf_machine(data)
346+
if mach != expected_machine:
347+
errors.append(
348+
f"{name}: ELF machine {mach} ({_MACHINE_NAME.get(mach, 'unknown')}) does "
349+
f"not match the '{subdir}' package arch {expected_machine} "
350+
f"({_MACHINE_NAME[expected_machine]}) -- wrong-arch/mislabeled driver."
351+
)
334352

335353
dyn = elf_dynamic(data)
336354
entries = _entries(effective_runpath(dyn))

‎tests/test_028_tls_connect_probe.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,31 @@ def test_split_top_level_handles_escaped_braces():
215215
]
216216

217217

218+
def test_split_top_level_inner_brace_is_literal():
219+
"""A braced value is SINGLE-LEVEL (matches _parse_braced_value): an inner '{' is a literal
220+
char, so '{a{b}' ends at the first '}' and ';Encrypt=no' is a separate segment -- not
221+
absorbed into Pwd (which would make force_tls emit a duplicate Encrypt)."""
222+
probe = _load_probe()
223+
assert probe._split_top_level("Pwd={a{b};Encrypt=no") == ["Pwd={a{b}", "Encrypt=no"]
224+
225+
226+
def test_split_top_level_mid_value_brace_is_literal():
227+
"""A '{' that is NOT the first char of a value is literal (the value is not braced), so
228+
'Server=a{b' is a simple value and ';' still splits -- matching the production parser."""
229+
probe = _load_probe()
230+
assert probe._split_top_level("Server=a{b;Encrypt=no") == ["Server=a{b", "Encrypt=no"]
231+
232+
233+
def test_force_tls_braced_password_with_inner_brace():
234+
"""End-to-end for the inner-'{' case: the user's Encrypt=no is dropped and Encrypt=yes
235+
appended exactly ONCE -- no duplicate 'encrypt' that mssql_python.connect would reject."""
236+
probe = _load_probe()
237+
out = probe.force_tls("Pwd={a{b};Encrypt=no").lower()
238+
assert "encrypt=yes" in out
239+
assert "encrypt=no" not in out
240+
assert out.count("encrypt=") == 1
241+
242+
218243
def test_redact_masks_values_and_flags_bare_segments():
219244
"""The debug line must never leak a value and must surface a no-value segment."""
220245
probe = _load_probe()

‎tests/test_029_bundled_binary_audit.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,22 @@ def test_audit_passes_openssl_alpha_upper_bound(tmp_path):
294294
assert not any("range-pinned" in e for e in errors)
295295

296296

297+
def test_audit_fails_openssl_upper_admits_4x(tmp_path):
298+
# '<=4', '<4.1', '<4.0.1' each admit some openssl 4.x -> must FAIL (looser than the pin).
299+
for spec in ("openssl >=3,<=4", "openssl >=3,<4.1", "openssl >=3,<4.0.1"):
300+
depends = ["python", "azure-identity", "krb5", "libtool", spec]
301+
errors = audit.audit_package(_make_pkg(tmp_path, depends=depends))
302+
assert any("range-pinned" in e for e in errors), spec
303+
304+
305+
def test_audit_passes_openssl_exclusive_4_variants(tmp_path):
306+
# '<4', '<4.0', '<4.0.0' all exclude every openssl 4.x and are accepted.
307+
for spec in ("openssl >=3,<4", "openssl >=3,<4.0", "openssl >=3,<4.0.0"):
308+
depends = ["python", "azure-identity", "krb5", "libtool", spec]
309+
errors = audit.audit_package(_make_pkg(tmp_path, depends=depends))
310+
assert not any("range-pinned" in e for e in errors), spec
311+
312+
297313
def test_audit_fails_driver_missing_from_one_subdir(tmp_path):
298314
# debian_ubuntu is complete, but rhel ships only libodbcinst (driver dropped). A
299315
# package-global count would pass since debian_ubuntu supplies a driver; per-subdir
@@ -435,3 +451,10 @@ def test_elf_machine_reads_arch():
435451
assert audit.elf_machine(_make_elf64()) == 62
436452
assert audit.elf_machine(_make_elf64(machine=183)) == 183
437453
assert audit.elf_machine(b"not an elf") is None
454+
455+
456+
def test_audit_fails_unknown_linux_subdir(tmp_path):
457+
# A linux subdir with no e_machine mapping (e.g. a future linux-ppc64le) must FAIL CLOSED,
458+
# not silently skip the architecture gate this audit exists to enforce.
459+
errors = audit.audit_package(_make_pkg(tmp_path, subdir="linux-ppc64le"))
460+
assert any("unrecognized Linux subdir" in e for e in errors)

‎tests/test_030_pe_machine_assert.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,27 @@ def test_non_windows_package_skipped(tmp_path):
220220
{"lib/python3.12/site-packages/mssql_python/_core.so": b"\x7fELF fake"},
221221
)
222222
assert ape.audit_package(p) == []
223+
224+
225+
@pytest.mark.skipif(not _zstd_available(), reason="no zstandard backend available")
226+
def test_win_conda_missing_pkg_payload_fails(tmp_path):
227+
# A .conda with info/index.json but NO pkg-*.tar.zst payload must FAIL (not silently pass
228+
# with zero members): iter_payload_members raises, and audit_package -> a violation.
229+
name = "mssql-python-1.13.0-py312_0"
230+
index = {
231+
"name": "mssql-python",
232+
"version": "1.13.0",
233+
"build": "py312_0",
234+
"subdir": "win-arm64",
235+
}
236+
info_buf = io.BytesIO()
237+
with tarfile.open(fileobj=info_buf, mode="w") as tf:
238+
idx = json.dumps(index).encode()
239+
ti = tarfile.TarInfo("info/index.json")
240+
ti.size = len(idx)
241+
tf.addfile(ti, io.BytesIO(idx))
242+
conda_path = tmp_path / f"{name}.conda"
243+
with zipfile.ZipFile(conda_path, "w") as zf:
244+
zf.writestr(f"info-{name}.tar.zst", _zstd_compress(info_buf.getvalue()))
245+
errors = ape.audit_package(str(conda_path))
246+
assert any("pkg-" in e for e in errors)

0 commit comments

Comments
 (0)