Harden wolfSSL and wolfSSH validation
Enable validated ECC imports and X25519 all-zero rejection through PUBLIC build policy. Tighten wolfSSH parser bounds, overflow handling, and signature framing with guard-page and crypto vector contracts.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
#!/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', 'DoChannelWindowAdjust', 'DoUserAuthRequestEcc',
|
||||
'DoUserAuthRequestEd25519')
|
||||
# Verify this slice cannot accidentally change ordering or existing password logic.
|
||||
from security_overrides import apply_edits, MODIFICATION_NOTICE, WOLFSSH_PARSER_EDITS
|
||||
baseline = MODIFICATION_NOTICE + apply_edits(original.read_text(), entry.edits[len(WOLFSSH_PARSER_EDITS):])
|
||||
for name in ('DoUserAuthRequestPassword', 'DoPacket', 'DoChannelFailure',
|
||||
'ParseRSAPubKey', 'ParseECCPubKey', 'DoUserAuthRequestPublicKey'):
|
||||
assert extract(generated.decode(), name) == extract(baseline, name), name
|
||||
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 = (
|
||||
('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')
|
||||
print('PASS: exact original hash; generated parser; unchanged ordering/password/deferred functions')
|
||||
print('NOTE: production build-tree registration/firmware not regenerated or validated')
|
||||
Reference in New Issue
Block a user