XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
"""Negative native guest proof: unavailable CPU entropy must refuse Bing HTTPS.

This creates its own bounded QEMU session and a fresh RAM-only image. A passing
negative proof is deliberately incompatible with the normal Open.ps1 receipt.
"""
import argparse
import json
from pathlib import Path
import re
import shutil
import socket
import subprocess
import time

from PIL import Image

from TestBoot import ROOT, Qmp, digest, inspect_image, read_json
from IndependentBingEvidence import (HOME, REMOTE, RESPONSE, native_navigation_failure,
    native_network_profile, network_arguments, window_geometry)

CPU = 'max,-rdrand,-rdseed'
REFUSAL = 'No healthy CPU cryptographic random source is available; HTTPS is refused.'
FATAL = ('XFE_NATIVE_FAIL', 'XFE_NATIVE_EXCEPTION', 'XFE_RUNTIME_FATAL', 'XFE_ZERO_FAILFAST',
         'XFE_INDEPENDENT_APPLICATION_FAILED', 'XFE_BROWSER_PAINT_FAILED')


def validate_negative_profile(arguments):
    for option, value in (('-cpu', CPU), ('-accel', 'tcg'), ('-rtc', 'base=utc'), ('-smp', '1')):
        indices = [index for index, item in enumerate(arguments) if item == option]
        assert len(indices) == 1 and indices[0] + 1 < len(arguments) and arguments[indices[0] + 1] == value, 'Wrong explicit negative CPU/RTC profile'
    actual = []
    for index, item in enumerate(arguments):
        if item in ('-net', '-netdev', '-nic', '-device'):
            assert index + 1 < len(arguments)
            actual.extend((item, arguments[index + 1]))
    assert actual == network_arguments(True), 'Negative proof must retain the real e1000/user NAT profile'


def verify_entropy_refusal(serial, after, window_id):
    fresh = serial[after:]
    assert not any(marker in fresh for marker in FATAL), 'The guest crashed rather than refusing HTTPS'
    assert RESPONSE + '_BEGIN' not in fresh and REMOTE + '_BEGIN' not in fresh, 'An unavailable RNG still produced remote response/DOM evidence'
    errors = re.findall(r'XFE_NATIVE_WEB_ERROR_BEGIN\r?\n(.*?)XFE_NATIVE_WEB_ERROR_END', fresh, re.S)
    assert any(REFUSAL in error for error in errors), 'No actual complete native hardware entropy rejection diagnostic'
    failure = native_navigation_failure(serial, after, window_id)
    assert failure is not None and REFUSAL in failure['Text'], 'The current actual Bing window did not show the explicit entropy refusal'
    return {key: value for key, value in failure.items() if key != 'Raw'}


class DeadlineQmp(Qmp):
    def __init__(self, port, deadline):
        self.deadline = deadline
        self.connection = socket.create_connection(('127.0.0.1', port), timeout=self.remaining())
        self.stream = self.connection.makefile('rwb'); self.number = 0
        try:
            assert 'QMP' in json.loads(self.stream.readline()), 'No QMP greeting'
            self.call('qmp_capabilities')
        except Exception:
            self.close()
            raise

    def remaining(self):
        remaining = self.deadline - time.monotonic()
        if remaining <= 0: raise TimeoutError('The owned negative guest phase deadline expired')
        return min(2, remaining)

    def call(self, command, arguments=None):
        self.connection.settimeout(self.remaining())
        return super().call(command, arguments)


def verify(kernel, boot_timeout=60, negative_timeout=15):
    assert 0 < boot_timeout <= 60 and 0 < negative_timeout <= 15, 'Negative proof deadlines exceed their bounds'
    kernel = Path(kernel).resolve()
    build_path = Path(str(kernel) + '.json'); build = read_json(build_path)
    assert build['Kind'] == 'XfeIndependentKernelBuild' and build['Passed'] is True and build['BootstrapOnly'] is False
    assert build['ContainsCosmos'] is False and build['ManagedRuntimeLinked'] is True and build['Target'] == 'x86_64-none'
    assert build['KernelSha256'] == digest(kernel)
    assert 'Source/XFESystem.NativeRuntime/Application/NativeApplication.csproj' in build['SourceSha256'], 'A real desktop build is required'
    for relative, expected in build['SourceSha256'].items():
        source = (ROOT / relative).resolve()
        assert source.is_relative_to(ROOT) and digest(source) == expected, 'Source changed: ' + relative
    proof = ROOT / 'build/IndependentKernel' / ('TlsEntropyFailure-' + str(time.time_ns()))
    proof.mkdir(parents=True); image = proof / 'System.img'; serial_path = proof / 'Serial.log'
    profile = native_network_profile(True); profile.update(Cpu=CPU, ExpectedEntropyAvailable=False)
    result = {'Kind': 'XfeIndependentTlsEntropyFailureProof', 'Passed': False, 'NegativeProfile': True,
              'ExpectedRefusal': REFUSAL, 'SelectedCpu': CPU, 'DisabledCpuInstructions': ['rdrand', 'rdseed'],
              'NativeNetworkProfile': profile, 'BingAcceptancePassed': False, 'NativeHttpsBingLoaded': False,
              'NetworkAcceptancePassed': False, 'DesktopAcceptancePassed': False, 'FullReleaseQualified': False,
              'ContainsCosmos': False, 'RamOnly': True, 'StorageEnabled': False, 'HostHttpRequests': False,
              'HostRandomFallback': False, 'BootTimeoutSeconds': boot_timeout, 'NegativeTimeoutSeconds': negative_timeout,
              'KernelPath': str(kernel), 'KernelSha256': digest(kernel), 'BuildReceipt': str(build_path),
              'BuildReceiptSha256': digest(build_path), 'ProofDirectory': str(proof), 'Screenshots': []}
    qemu = None; qmp = None; sources = None

    def serial():
        return serial_path.read_text(encoding='utf-8', errors='replace') if serial_path.exists() else ''

    def healthy():
        assert qemu is not None and qemu.poll() is None, 'The owned QEMU exited unexpectedly'
        value = serial(); assert not any(marker in value for marker in FATAL), value[-4096:]
        return value

    def wait(marker, after, deadline):
        while time.monotonic() < deadline:
            if marker in healthy()[after:]: return
            time.sleep(.05)
        raise TimeoutError('Native negative-proof acknowledgement timeout: ' + marker)

    def key(chord):
        qmp.call('human-monitor-command', {'command-line': 'sendkey ' + chord})
        time.sleep(.15)

    def screenshot(name):
        ppm = proof / (name + '.ppm'); png = proof / (name + '.png')
        try:
            qmp.call('screendump', {'filename': str(ppm)})
            with Image.open(ppm) as frame:
                assert frame.size == (1280, 800), 'Unexpected actual guest framebuffer geometry'
                frame.convert('RGB').save(png)
            capture = {'Path': png.name, 'Sha256': digest(png), 'NativeDimensions': [1280, 800]}
            result['Screenshots'].append(capture); return capture
        finally:
            if ppm.exists(): ppm.unlink()

    try:
        with (proof / 'PackImage.log').open('w', encoding='utf-8') as log:
            subprocess.run(['dotnet', 'run', '--project', str(ROOT / 'Tools/CSharpBoot/PackImage.csproj'), '-c', 'Release', '--no-build',
                            '--', str(kernel), str(image), '1280x800', '--independent-kernel'], cwd=ROOT, check=True, stdout=log, stderr=subprocess.STDOUT, timeout=60)
        inspected, files = inspect_image(image)
        assert image.stat().st_size == 128 * 1024 * 1024 and files['System/Kernel.elf'] == kernel.read_bytes()
        assert read_json(str(image) + '.json')['KernelImplementation'] == 'XFESystem independent kernel'
        result.update(ImagePath=str(image), ImageSha256=digest(image), ImageVerification=inspected)
        sources = {**build['SourceSha256'], **inspected['BootShimSourceSha256']}
        for relative in ('Tools/IndependentKernel/TestTlsEntropyFailure.py', 'Tools/IndependentKernel/TestBoot.py', 'Tools/IndependentKernel/IndependentBingEvidence.py'):
            sources[relative] = digest(ROOT / relative)
        snapshot = proof / 'SourceSnapshot.json'; snapshot.write_text(json.dumps(sources, indent=2), encoding='utf-8')
        result.update(SourceSnapshotPath=str(snapshot), SourceSnapshotSha256=digest(snapshot))
        firmware_code = ROOT / '.tools/firmware/edk2-x86_64-code.fd'
        firmware_vars = ROOT / '.tools/firmware/edk2-i386-vars.fd'
        lock = read_json(ROOT / 'Tools/CSharpBoot/Dependencies.json')['Firmware']
        assert digest(firmware_code) == lock[firmware_code.name] and digest(firmware_vars) == lock[firmware_vars.name], 'Pinned firmware changed'
        shutil.copy2(firmware_vars, proof / 'FirmwareVars.fd')
        with socket.socket() as reservation:
            reservation.bind(('127.0.0.1', 0)); port = reservation.getsockname()[1]
        command = [str(ROOT / '.tools/qemu/qemu-system-x86_64.exe'), '-machine', 'q35', '-accel', 'tcg', '-cpu', CPU, '-smp', '1', '-m', '1024M',
                   '-vga', 'std', '-global', 'VGA.vgamem_mb=64', '-display', 'none', *network_arguments(True), '-rtc', 'base=utc',
                   '-drive', f'if=pflash,format=raw,readonly=on,file={firmware_code}',
                   '-drive', f'if=pflash,format=raw,file={proof / "FirmwareVars.fd"}',
                   '-drive', f'file={image},format=raw,if=ide,snapshot=on', '-serial', 'file:' + str(serial_path),
                   '-qmp', f'tcp:127.0.0.1:{port},server=on,wait=off', '-no-reboot', '-no-shutdown']
        validate_negative_profile(command); result['QemuArguments'] = command
        with (proof / 'Qemu.log').open('w', encoding='utf-8') as log:
            qemu = subprocess.Popen(command, cwd=ROOT, stdout=log, stderr=subprocess.STDOUT, creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0))
            result['OwnedQemuProcessId'] = qemu.pid
            deadline = time.monotonic() + boot_timeout
            while qmp is None:
                healthy()
                try: qmp = DeadlineQmp(port, deadline)
                except (OSError, ConnectionError):
                    assert time.monotonic() < deadline, 'Negative-profile QMP boot deadline expired'
                    time.sleep(.05)
            for marker in ('XFE_NATIVE_SELFTEST_OK', 'XFE_APPLICATION_RUNTIME_CHECK_OK', 'XFE_INDEPENDENT_DESKTOP_FIRST_FRAME', 'XFE_DESKTOP_ACTIVE|Files'):
                wait(marker, 0, deadline)
            screenshot('Desktop-Before-Entropy-Rejection')
            after = len(serial()); deadline = time.monotonic() + negative_timeout; qmp.deadline = deadline
            for chord in ('ctrl-esc', 'down', 'down', 'ret'): key(chord)
            wait('XFE_DESKTOP_ACTIVE|Browser', after, deadline)
            error = None
            while time.monotonic() < deadline:
                value = healthy()
                try:
                    window_id, _ = window_geometry(value, 'Browser')
                    error = verify_entropy_refusal(value, after, window_id)
                    break
                except AssertionError:
                    time.sleep(.05)
            assert error is not None, 'Guest did not explicitly refuse missing CPU entropy within the negative deadline'
            result['ActualNavigationRefusal'] = error
            screenshot('Browser-Entropy-Refused')
            close_start = len(serial()); key('alt-f4'); wait('XFE_DESKTOP_ACTIVE|Files', close_start, deadline)
            restored = screenshot('Desktop-After-Entropy-Rejection')
            assert restored['Sha256'] != result['Screenshots'][-2]['Sha256'], 'Alt+F4 produced no changed actual guest framebuffer'
            verify_entropy_refusal(healthy(), after, error['WindowId'])
            result.update(EntropyRefusalPassed=True, NoRemoteResponseOrDom=True, AltF4ReturnedToResponsiveFiles=True, Passed=True)
    except Exception as error:
        result['Failure'] = str(error)
    finally:
        if qemu is not None:
            if qemu.poll() is None and qmp is not None:
                try:
                    qmp.deadline = time.monotonic() + 2; qmp.call('quit')
                except (OSError, RuntimeError, TimeoutError): pass
            try: qemu.wait(timeout=5)
            except subprocess.TimeoutExpired:
                qemu.terminate(); qemu.wait(timeout=5); result['ForcedCleanup'] = True
            result['QemuExitCode'] = qemu.returncode
        if qmp is not None: qmp.close()
        if serial_path.exists(): result['SerialEvidence'] = {'Path': serial_path.name, 'Sha256': digest(serial_path)}
        if image.exists(): result['ImageUnchanged'] = digest(image) == result.get('ImageSha256')
        result['KernelAndReceiptUnchanged'] = digest(kernel) == result['KernelSha256'] and digest(build_path) == result['BuildReceiptSha256']
        result['SourcesUnchanged'] = sources is not None and all((ROOT / relative).is_file() and digest(ROOT / relative) == expected for relative, expected in sources.items())
        result['Passed'] = (result['Passed'] and result.get('QemuExitCode') == 0 and not result.get('ForcedCleanup') and
                            result.get('ImageUnchanged') is True and result['KernelAndReceiptUnchanged'] and result['SourcesUnchanged'])
        (proof / 'Result.json').write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding='utf-8')
    print(json.dumps(result, ensure_ascii=True)); return result['Passed']


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('kernel', type=Path)
    parser.add_argument('--boot-timeout', type=int, choices=range(1, 61), default=60)
    parser.add_argument('--negative-timeout', type=int, choices=range(1, 16), default=15)
    args = parser.parse_args()
    raise SystemExit(0 if verify(args.kernel, args.boot_timeout, args.negative_timeout) else 1)