Require ELF closure verification when staging Linux speech runtime

This commit is contained in:
karl committed 2026-10-13 21:30:48 +02:00
1 parent e71cace91f
commit 61afa08b8b
3 files changed
+84

No files matched your search

+13
View File
@@ -51,3 +51,16 @@ The running original snapshot does not contain this correction. Its output
must not be released as corrected evidence. Next: wait for its terminal result,
inspect the complete ELF closure, then validate a corrected normal runtime
outside the build tree with `LD_LIBRARY_PATH` unset and forced CPU recognition.
Linux staging now invokes the same ELF closure verification after manifest,
architecture and CPU/acceleration checks. A valid inventory cannot bypass this
check; missing `readelf` also fails staging. Six targeted staging tests passed,
including inspected ELF coverage, failure propagation and Windows independence.
The upcoming real CPU test fixture was prepared from the unchanged verified
English WAV (source SHA-256
`0aa1fa97d367d987bfb3bb01d7eca11db77a544552ccb8dc0a78ad74d0b496ea`).
Exact mono 16-kHz signed PCM16 samples were converted to float32 by division by
32768, preserving all 360192 samples. PCM SHA-256:
`56948226ac5a0ad4b70523a856dad0851929c7067957f339e9a5b30fc9b369ce`.
Fixture preparation succeeded; native recognition has not yet been executed.
+12
View File
@@ -1,6 +1,7 @@
"""Verify and atomically stage the isolated normal speech runtime; no inference."""
import argparse
import hashlib
import importlib.util
import json
from pathlib import Path
import re
@@ -28,6 +29,15 @@ def input_path(root, name):
return Path(root) / 'tools' / (name if '/' in name else 'speech-runtime/' + name)
def verify_linux_closure(directory):
# Load our pinned builder, never code from the candidate runtime directory.
path = ROOT / 'tools/speech-runtime/build.py'
spec = importlib.util.spec_from_file_location('searchi_speech_build', path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
module.verify_linux_closure(directory)
def inventory(directory):
path = Path(directory) / 'runtime-manifest.json'
if path.is_symlink() or not path.is_file() or not 0 < path.stat().st_size <= 512 * 1024:
@@ -84,6 +94,8 @@ def verify_directory(directory, root=ROOT, platform='windows', architecture='x86
'VULKAN-LICENSE.txt', 'MSVC-REDISTRIBUTION.txt'} | CRT_LIBRARIES
if platform == 'windows' and not windows_libraries.issubset(names):
raise ValueError('Speech runtime lacks required shared libraries')
if platform == 'linux':
verify_linux_closure(directory)
records = {'speech-runtime/' + record['path']: record for record in manifest['files']}
manifest_path = directory / 'runtime-manifest.json'
records['speech-runtime/runtime-manifest.json'] = {
+59
View File
@@ -3,12 +3,71 @@ from pathlib import Path
import shutil
import tempfile
import unittest
from unittest import mock
import stage_speech_worker as speech
import test_package_windows as windows
class SpeechStagingTests(unittest.TestCase):
def linux_fixture(self, directory):
root, resources, _, _ = windows.PackageTests().fixture(directory)
runtime = resources / 'speech-runtime'
manifest_path = runtime / 'runtime-manifest.json'
manifest = json.loads(manifest_path.read_text())
for record in manifest['files']:
(runtime / record['path']).unlink()
manifest['files'] = []
for name in ('searchi-speech-worker', 'libggml-cpu-x64.so', 'libggml-vulkan.so'):
path = runtime / name
path.write_bytes(b'ELF fixture inspected by mocked readelf')
manifest['files'].append({'path': name, 'bytes': path.stat().st_size,
'sha256': speech.digest(path)})
manifest_path.write_text(json.dumps(manifest))
return root, runtime
def test_linux_hash_valid_inventory_requires_elf_closure(self):
with tempfile.TemporaryDirectory() as directory:
root, runtime = self.linux_fixture(directory)
with mock.patch.object(speech, 'identity', return_value=('linux', 'x86_64', None)), \
mock.patch.object(speech, 'verify_linux_closure') as closure:
records = speech.verify_directory(runtime, root, platform='linux')
closure.assert_called_once_with(runtime)
self.assertIn('speech-runtime/searchi-speech-worker', records)
def test_linux_closure_failure_and_missing_readelf_are_not_bypassed(self):
for error in (ValueError('Unpackaged Linux speech dependency'),
FileNotFoundError('readelf is required')):
with self.subTest(error=type(error)), tempfile.TemporaryDirectory() as directory:
root, runtime = self.linux_fixture(directory)
with mock.patch.object(speech, 'identity', return_value=('linux', 'x86_64', None)), \
mock.patch.object(speech, 'verify_linux_closure', side_effect=error):
with self.assertRaises(type(error)):
speech.verify_directory(runtime, root, platform='linux')
def test_windows_does_not_require_elf_tools(self):
with tempfile.TemporaryDirectory() as directory:
root, resources, _, _ = windows.PackageTests().fixture(directory)
with mock.patch.object(speech, 'verify_linux_closure') as closure:
speech.verify_directory(resources / 'speech-runtime', root)
closure.assert_not_called()
def test_loaded_builder_checks_every_linux_elf_and_rejects_build_tree_runpath(self):
with tempfile.TemporaryDirectory() as directory:
_, runtime = self.linux_fixture(directory)
valid = '(RUNPATH) Library runpath: [$ORIGIN]\n(NEEDED) Shared library: [libc.so.6]\n'
with mock.patch('subprocess.check_output', return_value=valid) as readelf:
speech.verify_linux_closure(runtime)
self.assertEqual(readelf.call_count, 3)
self.assertEqual({call.args[0][2] for call in readelf.call_args_list},
{str(runtime / name) for name in
('searchi-speech-worker', 'libggml-cpu-x64.so', 'libggml-vulkan.so')})
self.assertTrue(all(call.args[0][:2] == ['readelf', '--dynamic']
for call in readelf.call_args_list))
with mock.patch('subprocess.check_output', return_value=valid.replace('$ORIGIN', '/build/whisper')):
with self.assertRaisesRegex(ValueError, 'origin-only RUNPATH'):
speech.verify_linux_closure(runtime)
def test_replacement_prunes_obsolete_declared_code_and_keeps_closed_inventory(self):
with tempfile.TemporaryDirectory() as directory:
root, resources, _, _ = windows.PackageTests().fixture(directory)