Require ELF closure verification when staging Linux speech runtime
This commit is contained in:
1 parent
e71cace91f
commit
61afa08b8b
3 files changed
+84
No files matched your search
@@ -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'] = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user