#!/usr/bin/env python3 """Host contracts from freshly generated, original-hash-verified wolfSSH source.""" import os import resource from pathlib import Path import subprocess import sys import tempfile 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 entry = next(e for e in ENTRIES if e.name == 'wolfssh_internal') assert entry.sha256 == '81ff1f9166708abd5c2911e9fe57c0aee01c88b5d3f68c909ee8a856d37f36a9' original, generated = render_entry(entry, {'project': ROOT}) names = ('GetUint32', 'GetSize', 'GetString', 'GetSkip', 'GetStringRef', 'DoIgnore', 'DoServiceRequest', 'DoChannelFailure', 'DoChannelWindowAdjust', 'DoUserAuthRequestEcc', 'DoUserAuthRequestEd25519') # Parser edits must not change the independently applied ordering/password logic. from security_overrides import apply_edits, MODIFICATION_NOTICE, WOLFSSH_PARSER_EDITS baseline = MODIFICATION_NOTICE + apply_edits(original.read_text(), tuple( edit for edit in entry.edits if edit not in WOLFSSH_PARSER_EDITS)) for name in ('DoUserAuthRequestPassword', 'DoPacket', 'ParseRSAPubKey', 'ParseECCPubKey', 'DoUserAuthRequestPublicKey'): assert extract(generated.decode(), name) == extract(baseline, name), name from review import check_sources check_sources(original, generated) with tempfile.TemporaryDirectory(prefix='wolfssh-parser-') as directory: work = Path(directory) # Read back the actual generated bytes, not a parallel implementation. source = work / 'internal.c' source.write_bytes(generated) functions = '\n'.join(extract(source.read_text(), n) for n in names) (work / 'actual.c').write_text(functions) for small in (False, True): binary = work / ('contract-small' if small else 'contract') subprocess.run(['cc', '-std=gnu11', '-O2', '-Wall', '-Wextra', '-Werror', '-Wno-unused-parameter', '-fsanitize=undefined', '-fsanitize-undefined-trap-on-error', *(['-DWOLFSSH_SMALL_STACK'] if small else []), '-I', str(work), str(HERE / 'contract.c'), '-o', str(binary)], check=True, timeout=30, env={**os.environ, 'CCACHE_DISABLE': '1'}) subprocess.run([str(binary)], check=True, timeout=30) # Prove negative fixtures detect removal of each new boundary/type guard. # Mutations affect only temporary extracted host copies, never the override. mutations = ( ('Service exact length', 'DoServiceRequest', (('nameSz != sizeof("ssh-userauth") - 1 ||', '0 ||'),)), ('Service exact bytes', 'DoServiceRequest', (('WMEMCMP(serviceName, "ssh-userauth", sizeof("ssh-userauth") - 1) != 0', '0'),)), ('Failure bounded recipient', 'DoChannelFailure', (('ret = GetUint32(&channelId, buf, len, &begin);', 'ato32(buf + begin, &channelId); begin += 4; ret = WS_SUCCESS;'),)), ('Failure exact consumption', 'DoChannelFailure', (('if (begin != len)', 'if (0)'),)), ('Failure known recipient', 'DoChannelFailure', (('if (ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF) == NULL)', 'if (0 && ChannelFind(ssh, channelId, WS_CHANNEL_ID_SELF) == NULL)'),)), ('ECC nested read boundary', 'DoUserAuthRequestEcc', (('pk->signature, sz, &i)', 'pk->signature, pk->signatureSz, &i)'),)), ('ECC inner exact consumption', 'DoUserAuthRequestEcc', (('(i != sz || sz != pk->signatureSz)', '(sz != pk->signatureSz)'),)), ('ECC outer exact consumption', 'DoUserAuthRequestEcc', (('(i != sz || sz != pk->signatureSz)', '(i != sz)'),)), ('Ed25519 key label', 'DoUserAuthRequestEd25519', (('|| WMEMCMP(publicKeyType,', '&& WMEMCMP(publicKeyType,'),)), ('Ed25519 signature label', 'DoUserAuthRequestEd25519', (('pk->publicKeyTypeSz ||', 'pk->publicKeyTypeSz &&'),)), ('Ed25519 outer exact consumption', 'DoUserAuthRequestEd25519', (('if (ret == WS_SUCCESS && sz != pk->signatureSz - i)', 'if (0)'),)), ) resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) for label, name, replacements in mutations: body = extract(source.read_text(), name) changed = body for old, new in replacements: assert old in changed, label changed = changed.replace(old, new) assert changed != body, label (work / 'actual.c').write_text(functions.replace(body, changed)) binary = work / 'mutation' subprocess.run(['cc', '-std=gnu11', '-O2', '-Wall', '-Wextra', '-Werror', '-Wno-unused-parameter', '-I', str(work), str(HERE / 'contract.c'), '-o', str(binary)], check=True, timeout=30, env={**os.environ, 'CCACHE_DISABLE': '1'}) result = subprocess.run([str(binary)], capture_output=True, timeout=30) assert result.returncode != 0, f'Undetected mutation: {label}' print(f'PASS: {len(mutations)} parser guard-removal mutations rejected') from channel_request import run_contracts run_contracts(work, source.read_text(), extract) print('PASS: exact original hash; generated parser; parser-isolated ordering/password/deferred functions') print('NOTE: production build-tree registration/firmware not regenerated or validated')