- Enforce exact service and channel names with bounded failure parsing - Add hash-pinned offline notice assembly and regression coverage - Record advisory dispositions, provenance, integration evidence, and remaining gates
182 lines
8.9 KiB
Python
182 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Finite remaining-parser source/provenance contract; optional read-only profile replay."""
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
|
|
sys.dont_write_bytecode = True
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
HERE = Path(__file__).resolve().parent
|
|
sys.path.insert(0, str(ROOT / 'tests/wolfssh_auth_contract'))
|
|
from run import extract
|
|
from security_overrides import ENTRIES, render_entry
|
|
|
|
# Independently specified delta against the reviewed original+ordering+parser
|
|
# baseline. Reversing precisely these bytes must recover its whole-source hash.
|
|
BASELINE_SHA = '4948f8c447670eb54153dd1f3db69e4fa3092f7d7f7ed58a18a8fa05fcd168ca'
|
|
NOTICE = '''/* Server parser review modified 2026-09-16: bounded CHANNEL_FAILURE
|
|
* and ssh-userauth service validation; PR899/902 subset, not full PRs.
|
|
* Local follow-up: exact bounded channel-request names.
|
|
* Provenance/limits: docs/ssh_parser_remaining_review.md.
|
|
*/
|
|
'''
|
|
SERVICE = ''' /* PR902 current-server subset: reject before publishing the transition.
|
|
* The owner closes on this error; no best-effort disconnect is queued. */
|
|
if (nameSz != sizeof("ssh-userauth") - 1 ||
|
|
WMEMCMP(serviceName, "ssh-userauth", sizeof("ssh-userauth") - 1) != 0)
|
|
return WS_INVALID_STATE_E;
|
|
'''
|
|
OLD_FAILURE = ''' if (ssh == NULL || buf == NULL || len != 0 || idx == NULL)
|
|
ret = WS_BAD_ARGUMENT;
|
|
|
|
if (ret == WS_SUCCESS)
|
|
ret = WS_CHANOPEN_FAILED;'''
|
|
NEW_FAILURE = ''' word32 begin, channelId;
|
|
|
|
if (ssh == NULL || buf == NULL || idx == NULL)
|
|
return WS_BAD_ARGUMENT;
|
|
|
|
begin = *idx;
|
|
ret = GetUint32(&channelId, buf, len, &begin);
|
|
if (ret != WS_SUCCESS)
|
|
return ret;
|
|
if (begin != len)
|
|
return WS_BUFFER_E;
|
|
if (ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF) == NULL)
|
|
return WS_INVALID_CHANID;
|
|
|
|
*idx = begin;
|
|
ret = WS_CHANOPEN_FAILED;'''
|
|
PINS = {
|
|
'tools/wolfssh_order/delta.json': '6a81376fe3ffc5f449cde105402963f52d2d78cc844153e869a7e1e0f734fb76',
|
|
'managed_components/wolfssl__wolfssl/wolfcrypt/src/signature.c': '62ab3db3dfd251b2a2c73b69ef05aab6085d2e0d673fd9159514b3ee261cea4f',
|
|
}
|
|
PATCHES = {
|
|
'899': ('d2eeec5e263a4821c90805963eeb0666e99868a6', 'e33c8b0aaa3c5a5d6c5201e6141cafd2383a1ed6931fea6655a9b757fbe0b0ce'),
|
|
'902': ('ffa646a4b9d47d5d9d6127db140c433c58b1e276', '4dc3a69f8cecb34f5091b9c22e0012ea6020b7ede9c1ab3de4d96264168c345a'),
|
|
'918': ('fd82a4bcf55935f0801b14bca6be9c71e32ae914', 'a2a59707086c6273a2c339e63db49846a5a1862c73924b60a845a03d7d41be41'),
|
|
'919': ('0317c40fc131fab952d291d43c56c7b7ce5f4303', '753fffea9925deeed4ddfc7aeba1fce0e950602b52ed1434bd817c2d48857b2f'),
|
|
}
|
|
|
|
|
|
def digest(data):
|
|
return hashlib.sha256(data).hexdigest()
|
|
|
|
|
|
def check_sources(original, generated):
|
|
assert digest(original.read_bytes()) == \
|
|
'81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9'
|
|
text = generated.decode()
|
|
prior = text
|
|
for new, old in ((NOTICE, ''), (SERVICE, ''), (NEW_FAILURE, OLD_FAILURE)):
|
|
assert prior.count(new) == 1, 'Independent delta anchor changed'
|
|
prior = prior.replace(new, old)
|
|
# Independent name list/format, never imported from the generator's edits.
|
|
for name in ('env', 'shell', 'exec', 'subsystem', 'pty-req', 'window-change',
|
|
'exit-status', 'exit-signal', 'auth-agent-req@openssh.com'):
|
|
new = (f'typeSz == sizeof("{name}") - 1 &&\n'
|
|
f' WMEMCMP(type, "{name}", sizeof("{name}") - 1) == 0')
|
|
old = f'WSTRNCMP(type, "{name}", typeSz) == 0'
|
|
assert prior.count(new) == 1, name
|
|
prior = prior.replace(new, old)
|
|
assert digest(prior.encode()) == BASELINE_SHA, 'Unreviewed generated-source delta'
|
|
assert extract(prior, 'DoChannelRequest') == extract(original.read_text(), 'DoChannelRequest')
|
|
for path, sha in PINS.items():
|
|
assert digest((ROOT / path).read_bytes()) == sha, path
|
|
provenance = json.loads((HERE / 'provenance.json').read_text())
|
|
assert set(provenance) == set(PATCHES)
|
|
for number, (commit, sha) in PATCHES.items():
|
|
data = (HERE / f'pr{number}.patch').read_bytes()
|
|
assert digest(data) == sha and data.startswith(f'From {commit} '.encode())
|
|
assert provenance[number] == {
|
|
'url': f'https://patch-diff.githubusercontent.com/raw/wolfSSL/wolfssh/pull/{number}.patch',
|
|
'commit_url': f'https://github.com/wolfSSL/wolfssh/commit/{commit}.patch',
|
|
'commit': commit, 'sha256': sha,
|
|
}
|
|
for name in ('ParseRSAPubKey', 'ParseECCPubKey', 'ParsePubKey', 'DoKexDhReply',
|
|
'DoChannelOpen', 'DoGlobalRequest',
|
|
'DoServiceAccept'):
|
|
# DoKexDhReply has an ordering delta, checked by the whole baseline pin.
|
|
if name != 'DoKexDhReply':
|
|
assert extract(text, name) == extract(original.read_text(), name), name
|
|
# SignHEcdsa has preprocessor-selected bodies after its signature; compare
|
|
# its complete region rather than using the single-body extractor.
|
|
start, end = 'static int SignHEcdsa(', 'static int SignH('
|
|
def signer_region(source):
|
|
begin = source.index(start)
|
|
return source[begin:source.index(end, begin)]
|
|
assert signer_region(text) == signer_region(original.read_text())
|
|
assert len(re.findall(r'\bParsePubKey\s*\(', text)) == 2
|
|
assert 'ParsePubKey(ssh, sigKeyBlock_ptr, pubKey, pubKeySz)' in extract(text, 'DoKexDhReply')
|
|
assert len(re.findall(r'\bParseECCPubKey\s*\(', text)) == 2
|
|
assert len(re.findall(r'\bParseRSAPubKey\s*\(', text)) == 2
|
|
packet = extract(text, 'DoPacket')
|
|
assert packet.index('IsMessageAllowed(ssh, msg, WS_MSG_RECV)') < packet.index('switch (msg)')
|
|
print('PASS: independent whole-source delta, unchanged ordering/client/request branch bodies, archived PR provenance')
|
|
return prior.encode()
|
|
|
|
|
|
def check_profile(generated, prior):
|
|
databases = list((ROOT / '.pio/build').glob('*/compile_commands.json'))
|
|
assert len(databases) == 1, 'Need one unambiguous production compile database'
|
|
entries = json.loads(databases[0].read_text())
|
|
matches = []
|
|
for entry in entries:
|
|
path = (Path(entry['directory']) / entry['file']).resolve()
|
|
assert path != ROOT / 'managed_components/wolfssl__wolfssh/src/internal.c'
|
|
if path.parts[-3:] == ('security_overrides', 'wolfssh_internal', 'internal.c'):
|
|
matches.append((entry, path))
|
|
assert len(matches) == 1
|
|
entry, path = matches[0]
|
|
actual = path.read_bytes()
|
|
assert actual in (generated, prior), 'Unreviewed production generated input'
|
|
args = entry.get('arguments') or shlex.split(entry['command'])
|
|
clean = []; skip = False
|
|
for arg in args:
|
|
if skip:
|
|
skip = False
|
|
elif arg in ('-o', '-MF', '-MT', '-MQ'):
|
|
skip = True
|
|
elif arg not in ('-c', '-MD', '-MMD', '-MP'):
|
|
clean.append(arg)
|
|
result = subprocess.run(clean + ['-E', '-dM'], cwd=entry['directory'],
|
|
capture_output=True, text=True, check=True, timeout=60,
|
|
env={**os.environ, 'CCACHE_DISABLE': '1'})
|
|
macros = dict(re.findall(r'^#define (\w+)(?: (.*))?$', result.stdout, re.M))
|
|
assert macros.get('LIBWOLFSSH_VERSION_HEX') == '0x01004020'
|
|
for name in ('WOLFSSH_FWD', 'WOLFSSH_AGENT', 'WOLFSSH_CERTS', 'WOLFSSH_SFTP',
|
|
'WOLFSSH_SCP', 'WOLFSSH_NO_ECDSA', 'WOLFSSH_NO_ED25519',
|
|
'NO_WOLFSSH_SERVER', 'NO_WOLFSSH_CLIENT', 'WOLFSSH_SHELL'):
|
|
assert name not in macros, name
|
|
for name in ('WOLFSSH_NO_RSA', 'WOLFSSH_NO_DH', 'WOLFSSL_VALIDATE_ECC_IMPORT',
|
|
'WOLFSSL_ECDHX_SHARED_NOT_ZERO', 'CURVE25519_SMALL', 'ED25519_SMALL',
|
|
'WOLFSSH_TERM'):
|
|
assert name in macros, name
|
|
# Compile the fresh source with the saved real target flags, without changing
|
|
# the production build tree or creating objects/dependency files.
|
|
import tempfile
|
|
with tempfile.TemporaryDirectory(prefix='ssh-parser-syntax-') as directory:
|
|
fresh = Path(directory) / 'internal.c'; fresh.write_bytes(generated)
|
|
syntax = [str(fresh) if (Path(entry['directory']) / arg).resolve() == path
|
|
else arg for arg in clean]
|
|
subprocess.run(syntax + ['-fsyntax-only'], cwd=entry['directory'], check=True,
|
|
timeout=60, env={**os.environ, 'CCACHE_DISABLE': '1'})
|
|
print('PASS: actual Xtensa profile + fresh generated source syntax (no objects/build regeneration)')
|
|
print('Production generated source:', 'current' if actual == generated else
|
|
'REVIEWED PRIOR BASELINE; regeneration/build still required')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
assert sys.argv[1:] in ([], ['--profile'])
|
|
entry = next(e for e in ENTRIES if e.name == 'wolfssh_internal')
|
|
original, generated = render_entry(entry, {'project': ROOT})
|
|
prior = check_sources(original, generated)
|
|
print('Fresh generated SHA-256:', digest(generated))
|
|
if '--profile' in sys.argv:
|
|
check_profile(generated, prior)
|