feat(native): add protected JD final residency control

This commit is contained in:
冰朔 2026-08-07 00:27:55 +08:00
commit 8b5328993e
8 changed files with 503 additions and 24 deletions

View file

@ -0,0 +1,34 @@
schema: guanghu.jd-final-native-residency-control-gate/v1
gate_id: GH-OS-JD-FD-PRIMARY-001-FINAL-RESIDENCY-CONTROL
status: SOURCE_READY_QEMU_PASS_PHYSICAL_PENDING
node_id: JD-FD-PRIMARY
problem:
previous_native_default: PASS_100
previous_native_runtime_selected_recovery_itself: true
final_native_residency_proven: false
required_model:
normal_state: GUANGHU_OS_NATIVE_RESIDENT
native_runtime_autonomously_selects_recovery: false
outbound_relay_keeps_nat_path_alive: true
ordinary_reply: HLDP-NATIVE-ACK
recovery_reply: PER_DEPLOYMENT_128_BIT_CAPABILITY
recovery_request_authority: EXPLICIT_ROOT_ACTION_ON_BS-SG-003
token_repository_exposure: forbidden
wrong_capability: IGNORE_AND_REMAIN_NATIVE
qemu_acceptance:
native_initial_pipeline: PASS_100
native_remained_resident_before_authorized_recovery: PASS_100
wrong_capability_ignored: PASS_100
protected_recovery_capability_returned_to_linux: PASS_100
physical_acceptance:
source_published: pending
relay_installed: pending
one_time_final_candidate_booted: pending
sustained_native_residency_observed: pending
explicit_recovery_request_verified: pending
returned_linux_and_beacon_cleared: pending
final_native_residency_proven: false
boundary:
qemu_capability: 100
physical_server_capability: 0
do_not_claim_final_residency_until_physical_gate: true

View file

@ -216,6 +216,27 @@ long_mode_start:
jc native_block_proof_error jc native_block_proof_error
call ghal_block_persist_branch_receipt call ghal_block_persist_branch_receipt
jc native_world_store_error jc native_world_store_error
%ifdef GHOS_NATIVE_FINAL_RESIDENT
mov byte [rel physical_proof_flag], 0xa7
call ghal_block_write_proof
jc native_block_proof_error
.final_resident_loop:
; Keep the outbound NAT path alive. Recovery is selected by the protected
; relay capability, never autonomously by the native runtime.
mov byte [rel ghal_net_expected_sequence], 6
lea rax, [rel ghal_login_magic]
mov [rel ghal_net_expected_magic], rax
mov byte [rel ghal_net_command_kind], 0
call ghal_net_icmp_exchange_once
jc .final_resident_loop
cmp byte [rel ghal_net_matched_kind], 3
jne .final_resident_loop
call ghal_write_native_recovery_beacon
jc native_world_store_error
call ghal_block_write_proof
jc native_block_proof_error
jmp hardware_reset64
%else
%ifdef GHOS_NATIVE_RESIDENT %ifdef GHOS_NATIVE_RESIDENT
mov ecx, 10 mov ecx, 10
.resident_login_loop: .resident_login_loop:
@ -240,6 +261,7 @@ long_mode_start:
jc native_block_proof_error jc native_block_proof_error
call ghal_write_native_recovery_beacon call ghal_write_native_recovery_beacon
jc native_world_store_error jc native_world_store_error
%endif
%endif %endif
mov byte [rel physical_proof_flag], 0xa7 mov byte [rel physical_proof_flag], 0xa7
%endif %endif

View file

@ -1148,14 +1148,26 @@ ghal_net_icmp_exchange_once:
lea rdi, [rel ghal_native_ack_magic] lea rdi, [rel ghal_native_ack_magic]
mov ecx, GHOS_LOGIN_MAGIC_SIZE mov ecx, GHOS_LOGIN_MAGIC_SIZE
repe cmpsb repe cmpsb
je .expected_magic_match
%ifdef GHOS_NATIVE_FINAL_RESIDENT
cmp byte [rel ghal_net_command_kind], 0
jne .ignore_packet jne .ignore_packet
jmp .expected_magic_match mov rsi, VIRTIO_NET_RX_BUFFER + GHOS_LOGIN_MAGIC_OFFSET + GHOS_LOGIN_MAGIC_SIZE
lea rdi, [rel ghal_recovery_capability]
mov ecx, GHOS_LOGIN_MAGIC_SIZE
repe cmpsb
jne .ignore_packet
mov byte [rel ghal_net_matched_kind], 3
jmp .matched_kind_ready
%else
jmp .ignore_packet
%endif
.expected_magic_match: .expected_magic_match:
mov al, [rel ghal_net_command_kind] mov al, [rel ghal_net_command_kind]
.store_matched_kind:
mov [rel ghal_net_matched_kind], al mov [rel ghal_net_matched_kind], al
.matched_kind_ready:
cmp byte [rel ghal_net_matched_kind], 0 cmp byte [rel ghal_net_matched_kind], 0
jne .record_command jne .record_command
mov byte [rel physical_proof_ipv4_rx_verified], 1 mov byte [rel physical_proof_ipv4_rx_verified], 1
@ -1299,6 +1311,9 @@ ghal_code_commit_magic: db "HLDP-CODE-COMMIT"
ghal_branch_move_magic: db "HLDP-BRANCH-MOVE" ghal_branch_move_magic: db "HLDP-BRANCH-MOVE"
ghal_recovery_magic: db "HLDP-RECOVER-OS!" ghal_recovery_magic: db "HLDP-RECOVER-OS!"
ghal_native_ack_magic: db "HLDP-NATIVE-ACK!" ghal_native_ack_magic: db "HLDP-NATIVE-ACK!"
%ifdef GHOS_NATIVE_FINAL_RESIDENT
%include "recovery-token.inc"
%endif
ghal_branch_receipt_template: ghal_branch_receipt_template:
db "GHOS_BRANCH_MAIN_V1", 10 db "GHOS_BRANCH_MAIN_V1", 10
db "channel=HLP-MOD-CODE-CHANNEL", 10 db "channel=HLP-MOD-CODE-CHANNEL", 10

View file

@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -euo pipefail
[[ $# -eq 3 ]] || {
echo "usage: build-native-final-resident-candidate.sh <world-root> <output-directory> <recovery-token-file>" >&2
exit 64
}
world_root=$(readlink -f "$1")
output_root=$(readlink -m "$2")
token_file=$(readlink -f "$3")
source_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
native_root=${source_root}/native/x86_64-bios
command -v nasm >/dev/null
token_hex=$(tr -d '\r\n ' <"${token_file}")
[[ ${token_hex} =~ ^[0-9a-fA-F]{32}$ ]] || {
echo "recovery token must be exactly 16 bytes encoded as 32 hexadecimal characters" >&2
exit 65
}
mkdir -p "${output_root}"
chmod 0700 "${output_root}"
cargo run --quiet --manifest-path "${source_root}/Cargo.toml" \
-p hldp-native-compiler -- "${world_root}" "${output_root}/world.inc"
{
printf 'ghal_recovery_capability: db '
for index in $(seq 0 15); do
[[ ${index} -eq 0 ]] || printf ','
printf '0x%s' "${token_hex:$((index * 2)):2}"
done
printf '\n'
} >"${output_root}/recovery-token.inc"
chmod 0600 "${output_root}/recovery-token.inc"
layout_args=()
while IFS= read -r argument; do layout_args+=("${argument}"); done \
< <("${source_root}/scripts/native-layout-nasm-args.sh" "${world_root}")
(
cd "${output_root}"
nasm -f bin -I "${output_root}/" -I "${native_root}/" \
"${layout_args[@]}" \
-dGHOS_PHYSICAL_CANDIDATE=1 \
-dGHOS_NATIVE_RESIDENT=1 \
-dGHOS_NATIVE_FINAL_RESIDENT=1 \
"${native_root}/boot.asm" \
-o guanghu-os-x86_64-bios-final-resident.img
)
[[ $(stat -c %s "${output_root}/guanghu-os-x86_64-bios-final-resident.img") -eq 14848 ]]
sha256sum "${output_root}/guanghu-os-x86_64-bios-final-resident.img" \
>"${output_root}/guanghu-os-x86_64-bios-final-resident.img.sha256"

View file

@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -euo pipefail
[[ $# -eq 5 ]] || {
echo "usage: install-jd-final-resident-relay.sh <peer-script> <recovery-token-file> <allowed-native-source> <request-file> <receipt-file>" >&2
exit 64
}
[[ ${EUID} -eq 0 ]] || exit 77
peer_script=$(readlink -f "$1")
token_file=$(readlink -f "$2")
allowed_source=$3
request_file=$(readlink -m "$4")
receipt_file=$(readlink -m "$5")
token_hex=$(tr -d '\r\n ' <"${token_file}")
[[ ${token_hex} =~ ^[0-9a-fA-F]{32}$ ]] || exit 65
[[ ${allowed_source} =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || exit 65
[[ ${request_file} == /run/guanghu-jd-native-final-recovery.request ]] || exit 65
[[ ${receipt_file} == /var/lib/guanghu-os/jd-final-resident-relay.hldp ]] || exit 65
install -D -m 0755 "${peer_script}" \
/usr/local/libexec/guanghu-os/physical-native-icmp-peer.py
install -D -m 0600 "${token_file}" \
/etc/guanghu-os/jd-final-resident-recovery-token.hex
install -d -m 0700 /var/lib/guanghu-os
rm -f "${request_file}"
install -m 0644 /dev/stdin \
/etc/systemd/system/guanghu-jd-native-final-resident-relay.service <<EOF
[Unit]
Description=Protected relay for JD Guanghu OS final native residency
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/local/libexec/guanghu-os/physical-native-icmp-peer.py --raw-socket --final-resident-relay --allowed-source ${allowed_source} --recovery-token-file /etc/guanghu-os/jd-final-resident-recovery-token.hex --recovery-request-file ${request_file} --receipt ${receipt_file} --timeout 604800
Restart=always
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/guanghu-os /run
RestrictAddressFamilies=AF_INET
AmbientCapabilities=CAP_NET_RAW
CapabilityBoundingSet=CAP_NET_RAW
[Install]
WantedBy=multi-user.target
EOF
install -D -m 0755 /dev/stdin \
/usr/local/sbin/guanghu-request-jd-final-native-recovery <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
[[ ${EUID} -eq 0 ]] || exit 77
receipt=/var/lib/guanghu-os/jd-final-resident-relay.hldp
request=/run/guanghu-jd-native-final-recovery.request
grep -q '^status: READY_NATIVE_RESIDENT$' "${receipt}"
install -m 0600 /dev/null "${request}"
echo "authorized recovery request armed"
EOF
install -D -m 0755 /dev/stdin \
/usr/local/sbin/guanghu-clear-jd-final-native-recovery-request <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
[[ ${EUID} -eq 0 ]] || exit 77
rm -f /run/guanghu-jd-native-final-recovery.request
echo "recovery request cleared"
EOF
systemctl daemon-reload
systemctl enable guanghu-jd-native-final-resident-relay.service >/dev/null
echo "installed but not started"

View file

@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import os
import pathlib import pathlib
import socket import socket
import struct import struct
@ -72,12 +73,17 @@ def verify_native_request(packet: bytes, pipeline: list[bytes]) -> tuple[int, st
return sequence, source return sequence, source
def native_ack_reply(packet: bytes) -> bytes: def native_ack_reply(
packet: bytes,
response_capability: bytes = NATIVE_ACK_MAGIC,
) -> bytes:
if len(response_capability) != 16:
raise ValueError("response capability must be exactly 16 bytes")
header_length = (packet[0] & 0x0F) * 4 header_length = (packet[0] & 0x0F) * 4
icmp = bytearray(packet[header_length:]) icmp = bytearray(packet[header_length:])
icmp[0] = 0 icmp[0] = 0
icmp[2:4] = b"\0\0" icmp[2:4] = b"\0\0"
icmp[32:48] = NATIVE_ACK_MAGIC icmp[32:48] = response_capability
icmp[2:4] = struct.pack("!H", checksum(bytes(icmp))) icmp[2:4] = struct.pack("!H", checksum(bytes(icmp)))
return bytes(icmp) return bytes(icmp)
@ -122,6 +128,9 @@ def main() -> None:
parser.add_argument("--resume-resident-count", type=int) parser.add_argument("--resume-resident-count", type=int)
parser.add_argument("--resident-pipeline", action="store_true") parser.add_argument("--resident-pipeline", action="store_true")
parser.add_argument("--native-relay", action="store_true") parser.add_argument("--native-relay", action="store_true")
parser.add_argument("--final-resident-relay", action="store_true")
parser.add_argument("--recovery-token-file")
parser.add_argument("--recovery-request-file")
parser.add_argument("--allowed-source") parser.add_argument("--allowed-source")
parser.add_argument("--native-ack-delay", type=float, default=0.25) parser.add_argument("--native-ack-delay", type=float, default=0.25)
parser.add_argument("--native-ack-repetitions", type=int, default=4) parser.add_argument("--native-ack-repetitions", type=int, default=4)
@ -149,37 +158,66 @@ def main() -> None:
args.resume_resident_count is not None, args.resume_resident_count is not None,
args.resident_pipeline, args.resident_pipeline,
args.native_relay, args.native_relay,
args.final_resident_relay,
) )
) > 1: ) > 1:
raise SystemExit( raise SystemExit(
"--resident, --login-only, --resume-after-login, and " "--resident, --login-only, --resume-after-login, and "
"--resume-after-commit, --resume-after-branch, and " "--resume-after-commit, --resume-after-branch, and "
"--resume-resident-count, --resident-pipeline, and --native-relay " "--resume-resident-count, --resident-pipeline, --native-relay, "
"and --final-resident-relay "
"are mutually exclusive" "are mutually exclusive"
) )
socket_type = socket.SOCK_RAW if args.raw_socket else socket.SOCK_DGRAM socket_type = socket.SOCK_RAW if args.raw_socket else socket.SOCK_DGRAM
peer = socket.socket(socket.AF_INET, socket_type, socket.IPPROTO_ICMP) peer = socket.socket(socket.AF_INET, socket_type, socket.IPPROTO_ICMP)
deadline = time.monotonic() + args.timeout deadline = time.monotonic() + args.timeout
if args.native_relay: if args.native_relay or args.final_resident_relay:
if not args.raw_socket: if not args.raw_socket:
raise SystemExit("--native-relay requires --raw-socket") raise SystemExit("native relay modes require --raw-socket")
if not args.allowed_source: if not args.allowed_source:
raise SystemExit("--native-relay requires --allowed-source") raise SystemExit("native relay modes require --allowed-source")
recovery_capability = None
if args.final_resident_relay:
if not args.recovery_token_file or not args.recovery_request_file:
raise SystemExit(
"--final-resident-relay requires --recovery-token-file "
"and --recovery-request-file"
)
token_hex = (
pathlib.Path(args.recovery_token_file)
.read_text(encoding="ascii")
.strip()
)
recovery_capability = bytes.fromhex(token_hex)
if len(recovery_capability) != 16:
raise SystemExit("recovery token must be exactly 16 bytes")
allowed_source = socket.gethostbyname(args.allowed_source) allowed_source = socket.gethostbyname(args.allowed_source)
pipeline = [ pipeline = (
LOGIN_MAGIC, [
LOGIN_MAGIC, LOGIN_MAGIC,
LOGIN_MAGIC, LOGIN_MAGIC,
COMMIT_MAGIC, LOGIN_MAGIC,
BRANCH_MAGIC, COMMIT_MAGIC,
*([LOGIN_MAGIC] * 10), BRANCH_MAGIC,
RECOVERY_MAGIC, LOGIN_MAGIC,
] ]
if args.final_resident_relay
else [
LOGIN_MAGIC,
LOGIN_MAGIC,
LOGIN_MAGIC,
COMMIT_MAGIC,
BRANCH_MAGIC,
*([LOGIN_MAGIC] * 10),
RECOVERY_MAGIC,
]
)
peer.settimeout(0.5) peer.settimeout(0.5)
acknowledged: set[int] = set() acknowledged: set[int] = set()
sources: set[str] = set() sources: set[str] = set()
completed_at: float | None = None completed_at: float | None = None
recovery_capability_sent = False
while time.monotonic() < deadline and ( while time.monotonic() < deadline and (
completed_at is None or time.monotonic() < completed_at + 5.0 completed_at is None or time.monotonic() < completed_at + 5.0
): ):
@ -196,7 +234,15 @@ def main() -> None:
# Repeat the authenticated frame because the physical JD path can # Repeat the authenticated frame because the physical JD path can
# lose a single raw-socket reply after NAT has been established. # lose a single raw-socket reply after NAT has been established.
time.sleep(args.native_ack_delay) time.sleep(args.native_ack_delay)
reply = native_ack_reply(packet) response_capability = NATIVE_ACK_MAGIC
if (
args.final_resident_relay
and sequence == 6
and os.path.isfile(args.recovery_request_file)
):
response_capability = recovery_capability
recovery_capability_sent = True
reply = native_ack_reply(packet, response_capability)
for repetition in range(args.native_ack_repetitions): for repetition in range(args.native_ack_repetitions):
peer.sendto(reply, address) peer.sendto(reply, address)
if repetition + 1 < args.native_ack_repetitions: if repetition + 1 < args.native_ack_repetitions:
@ -208,8 +254,39 @@ def main() -> None:
) )
acknowledged.add(sequence) acknowledged.add(sequence)
sources.add(source) sources.add(source)
if len(acknowledged) == len(pipeline) and completed_at is None: if args.final_resident_relay:
ready = all(sequence_id in acknowledged for sequence_id in range(1, 7))
receipt = pathlib.Path(args.receipt)
receipt.write_text(
"schema: guanghu.physical-native-final-resident-relay/v1\n"
f"status: {'RECOVERY_CAPABILITY_SENT' if recovery_capability_sent else 'READY_NATIVE_RESIDENT' if ready else 'STARTING'}\n"
"handshake_direction: NATIVE_INITIATED_OUTBOUND_ICMP\n"
"native_ack_marker: HLDP-NATIVE-ACK!\n"
"relay_cannot_select_command: true\n"
"recovery_requires_root_owned_request_file: true\n"
f"recovery_capability_sent: {str(recovery_capability_sent).lower()}\n"
f"acknowledged_sequences: {','.join(str(item) for item in sorted(acknowledged))}\n"
f"observed_nat_sources: {','.join(sorted(sources))}\n",
encoding="utf-8",
)
if ready and recovery_capability_sent and completed_at is None:
completed_at = time.monotonic()
elif len(acknowledged) == len(pipeline) and completed_at is None:
completed_at = time.monotonic() completed_at = time.monotonic()
if args.final_resident_relay:
if not all(sequence_id in acknowledged for sequence_id in range(1, 7)):
raise TimeoutError(
"native final-resident relay did not reach ready state; "
"acknowledged sequences: "
+ ",".join(str(item) for item in sorted(acknowledged))
)
if not recovery_capability_sent:
raise TimeoutError(
"native final-resident relay remained ready but no "
"authorized recovery request was observed"
)
print(pathlib.Path(args.receipt).read_text(encoding="utf-8"), end="")
return
if len(acknowledged) != len(pipeline): if len(acknowledged) != len(pipeline):
raise TimeoutError( raise TimeoutError(
"native relay pipeline incomplete; acknowledged sequences: " "native relay pipeline incomplete; acknowledged sequences: "

View file

@ -78,10 +78,12 @@ def ordinary_reply(frame: bytes) -> bytes:
return bytes(reply) return bytes(reply)
def authenticated_reply(frame: bytes, magic: bytes) -> bytes: def authenticated_reply(frame: bytes, response_capability: bytes = NATIVE_ACK_MAGIC) -> bytes:
if len(response_capability) != 16:
raise ValueError("response capability must be exactly 16 bytes")
reply = bytearray(ordinary_reply(frame)) reply = bytearray(ordinary_reply(frame))
reply[36:38] = b"\0\0" reply[36:38] = b"\0\0"
reply[66:82] = NATIVE_ACK_MAGIC reply[66:82] = response_capability
reply[36:38] = struct.pack("!H", checksum(bytes(reply[34:82]))) reply[36:38] = struct.pack("!H", checksum(bytes(reply[34:82])))
return bytes(reply) return bytes(reply)
@ -93,11 +95,23 @@ def main() -> None:
parser.add_argument("--qemu-port", type=int, required=True) parser.add_argument("--qemu-port", type=int, required=True)
parser.add_argument("--receipt", required=True) parser.add_argument("--receipt", required=True)
parser.add_argument("--resident", action="store_true") parser.add_argument("--resident", action="store_true")
parser.add_argument("--final-resident", action="store_true")
parser.add_argument("--recovery-token-file")
parser.add_argument("--login-only", action="store_true") parser.add_argument("--login-only", action="store_true")
parser.add_argument("--guest-ip", default="172.16.0.6") parser.add_argument("--guest-ip", default="172.16.0.6")
parser.add_argument("--peer-ip", default="172.16.0.1") parser.add_argument("--peer-ip", default="172.16.0.1")
parser.add_argument("--relay-ip", default="43.153.193.169") parser.add_argument("--relay-ip", default="43.153.193.169")
args = parser.parse_args() args = parser.parse_args()
if sum((args.resident, args.final_resident, args.login_only)) > 1:
raise SystemExit("--resident, --final-resident and --login-only are mutually exclusive")
if args.final_resident and not args.recovery_token_file:
raise SystemExit("--final-resident requires --recovery-token-file")
recovery_capability = None
if args.recovery_token_file:
token_hex = open(args.recovery_token_file, encoding="ascii").read().strip()
recovery_capability = bytes.fromhex(token_hex)
if len(recovery_capability) != 16:
raise SystemExit("recovery token must be exactly 16 bytes")
GUEST_IP = socket.inet_aton(args.guest_ip) GUEST_IP = socket.inet_aton(args.guest_ip)
GATEWAY_IP = socket.inet_aton(args.peer_ip) GATEWAY_IP = socket.inet_aton(args.peer_ip)
RELAY_IP = socket.inet_aton(args.relay_ip) RELAY_IP = socket.inet_aton(args.relay_ip)
@ -135,6 +149,9 @@ def main() -> None:
elif sequence == 5: elif sequence == 5:
magic = BRANCH_MAGIC magic = BRANCH_MAGIC
branch_verified = True branch_verified = True
elif args.final_resident and sequence == 6:
magic = LOGIN_MAGIC
resident_login_count += 1
elif 6 <= sequence <= 15: elif 6 <= sequence <= 15:
magic = LOGIN_MAGIC magic = LOGIN_MAGIC
resident_login_count += 1 resident_login_count += 1
@ -146,14 +163,26 @@ def main() -> None:
verified_request(frame, sequence, magic) verified_request(frame, sequence, magic)
peer.sendto(ordinary_reply(frame), qemu) peer.sendto(ordinary_reply(frame), qemu)
time.sleep(0.05) time.sleep(0.05)
reply = authenticated_reply(frame, magic) response_capability = NATIVE_ACK_MAGIC
if args.final_resident and resident_login_count >= 3:
response_capability = recovery_capability
recovery_verified = True
reply = authenticated_reply(frame, response_capability)
for repetition in range(4): for repetition in range(4):
peer.sendto(reply, qemu) peer.sendto(reply, qemu)
if repetition < 3: if repetition < 3:
time.sleep(0.01) time.sleep(0.01)
terminal_sequence = 3 if args.login_only else (16 if args.resident else 5) terminal = (
if sequence != terminal_sequence: sequence == 3
if args.login_only
else recovery_verified
if args.final_resident
else sequence == 16
if args.resident
else sequence == 5
)
if not terminal:
continue continue
with open(args.receipt, "w", encoding="utf-8") as output: with open(args.receipt, "w", encoding="utf-8") as output:
output.write( output.write(
@ -169,6 +198,8 @@ def main() -> None:
f"branch_move_reply_verified: {str(branch_verified).lower()}\n" f"branch_move_reply_verified: {str(branch_verified).lower()}\n"
f"resident_login_reply_count: {resident_login_count}\n" f"resident_login_reply_count: {resident_login_count}\n"
f"recovery_reply_verified: {str(recovery_verified).lower()}\n" f"recovery_reply_verified: {str(recovery_verified).lower()}\n"
f"final_resident_control: {str(args.final_resident).lower()}\n"
"recovery_selected_by_native: false\n"
"login_magic: HLDP-GHOS-LOGIN!\n" "login_magic: HLDP-GHOS-LOGIN!\n"
) )
return return

View file

@ -0,0 +1,172 @@
#!/usr/bin/env bash
set -euo pipefail
[[ $# -eq 3 ]] || {
echo "usage: test-native-final-resident-candidate.sh <candidate-image> <recovery-token-file> <receipt-output>" >&2
exit 64
}
candidate=$(readlink -f "$1")
token_file=$(readlink -f "$2")
receipt=$(readlink -m "$3")
source_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
native_root=${source_root}/native/x86_64-bios
test_root=$(mktemp -d)
peer_pid=
cleanup() {
if [[ -n ${peer_pid} ]]; then
kill "${peer_pid}" 2>/dev/null || true
fi
if [[ ${GHOS_KEEP_TEST_ROOT:-0} != 1 ]]; then
rm -rf "${test_root}"
else
echo "GHOS_TEST_ROOT=${test_root}" >&2
fi
}
trap cleanup EXIT
[[ $(stat -c %s "${candidate}") -eq 14848 ]]
[[ $(tr -d '\r\n ' <"${token_file}" | wc -c) -eq 32 ]]
python3 - "${token_file}" "${test_root}/wrong-token.hex" <<'PY'
import pathlib
import sys
token = bytearray.fromhex(pathlib.Path(sys.argv[1]).read_text().strip())
token[0] ^= 0x01
pathlib.Path(sys.argv[2]).write_text(token.hex() + "\n", encoding="ascii")
PY
truncate -s 700M "${test_root}/disk.img"
nasm -f bin -dCANDIDATE_LBA=105 -dPROOF_LBA=134 \
"${native_root}/physical-test-mbr.asm" -o "${test_root}/mbr.bin"
dd if="${test_root}/mbr.bin" of="${test_root}/disk.img" \
bs=512 seek=0 conv=notrunc status=none
dd if="${candidate}" of="${test_root}/disk.img" \
bs=512 seek=105 conv=notrunc status=none
peer_port=$((33000 + ${BASHPID:-$$} % 1000))
qemu_port=$((peer_port + 1))
python3 "${source_root}/scripts/qemu-native-net-peer.py" \
--listen-port "${peer_port}" \
--qemu-port "${qemu_port}" \
--receipt "${test_root}/peer.hldp" \
--final-resident \
--recovery-token-file "${token_file}" >"${test_root}/peer.log" 2>&1 &
peer_pid=$!
set +e
timeout 40 qemu-system-x86_64 \
-machine pc,accel=tcg \
-m 64M \
-drive "if=none,id=ghboot,format=raw,file=${test_root}/disk.img" \
-device virtio-blk-pci,drive=ghboot,disable-modern=on,bootindex=0 \
-netdev "dgram,id=ghnet,local.type=inet,local.host=127.0.0.1,local.port=${qemu_port},remote.type=inet,remote.host=127.0.0.1,remote.port=${peer_port}" \
-device virtio-net-pci,netdev=ghnet,disable-modern=on,mac=52:54:00:26:71:98 \
-display none \
-monitor none \
-serial "file:${test_root}/serial.log" \
-device isa-debug-exit,iobase=0xf4,iosize=0x04
qemu_status=$?
set -e
[[ ${qemu_status} -eq 33 ]]
wait "${peer_pid}"
peer_pid=
grep -q '^final_resident_control: true$' "${test_root}/peer.hldp"
grep -q '^resident_login_reply_count: 3$' "${test_root}/peer.hldp"
grep -q '^recovery_reply_verified: true$' "${test_root}/peer.hldp"
grep -q '^recovery_selected_by_native: false$' "${test_root}/peer.hldp"
grep -q '^GHOS_NATIVE_RECOVERY_BEACON=WRITE_READ_VERIFIED' "${test_root}/serial.log"
grep -q '^GHOS_DISK_PROOF_OBSERVED_AFTER_RESET=LBA134' "${test_root}/serial.log"
python3 - "${test_root}/disk.img" <<'PY'
import pathlib
import sys
with pathlib.Path(sys.argv[1]).open("rb") as disk:
def sector(lba: int, count: int = 1) -> bytes:
disk.seek(lba * 512)
return disk.read(count * 512)
proof = sector(134)
assert proof[0] == 0xA7
assert proof[1:].startswith(b"GHOS_NATIVE_LONG64_DISK_PROOF\0")
assert proof[99:102] == bytes([1, 1, 1])
assert proof[102:105] == bytes([1, 1, 1])
assert proof[105] == 6
assert sector(139, 2).startswith(b"# GRUB Environment Block\n")
assert sector(141).startswith(b"GHOS_GHCIP_INDEX_V1\n")
PY
truncate -s 700M "${test_root}/wrong-capability-disk.img"
dd if="${test_root}/mbr.bin" of="${test_root}/wrong-capability-disk.img" \
bs=512 seek=0 conv=notrunc status=none
dd if="${candidate}" of="${test_root}/wrong-capability-disk.img" \
bs=512 seek=105 conv=notrunc status=none
wrong_peer_port=$((peer_port + 2))
wrong_qemu_port=$((qemu_port + 2))
python3 "${source_root}/scripts/qemu-native-net-peer.py" \
--listen-port "${wrong_peer_port}" \
--qemu-port "${wrong_qemu_port}" \
--receipt "${test_root}/wrong-peer.hldp" \
--final-resident \
--recovery-token-file "${test_root}/wrong-token.hex" \
>"${test_root}/wrong-peer.log" 2>&1 &
peer_pid=$!
set +e
timeout 5 qemu-system-x86_64 \
-machine pc,accel=tcg \
-m 64M \
-drive "if=none,id=ghboot,format=raw,file=${test_root}/wrong-capability-disk.img" \
-device virtio-blk-pci,drive=ghboot,disable-modern=on,bootindex=0 \
-netdev "dgram,id=ghnet,local.type=inet,local.host=127.0.0.1,local.port=${wrong_qemu_port},remote.type=inet,remote.host=127.0.0.1,remote.port=${wrong_peer_port}" \
-device virtio-net-pci,netdev=ghnet,disable-modern=on,mac=52:54:00:26:71:98 \
-display none \
-monitor none \
-serial "file:${test_root}/wrong-capability-serial.log" \
-device isa-debug-exit,iobase=0xf4,iosize=0x04
wrong_qemu_status=$?
set -e
[[ ${wrong_qemu_status} -eq 124 ]]
wait "${peer_pid}"
peer_pid=
python3 - "${test_root}/wrong-capability-disk.img" <<'PY'
import pathlib
import sys
with pathlib.Path(sys.argv[1]).open("rb") as disk:
disk.seek(134 * 512)
proof = disk.read(512)
disk.seek(139 * 512)
recovery = disk.read(1024)
assert proof[0] == 0xA7
assert recovery == bytes(1024)
PY
observed_at=$(date --iso-8601=seconds)
image_sha=$(sha256sum "${candidate}" | awk '{print $1}')
cat >"${receipt}" <<EOF
schema: guanghu.jd-native-final-resident-qemu-test/v1
receipt_id: GH-OS-JD-FD-PRIMARY-001-NATIVE-FINAL-RESIDENT-CONTROL-QEMU-001
status: PASS_100
observed_at: ${observed_at}
node_id: JD-FD-PRIMARY
candidate:
lba_start: 105
sector_count: 29
sha256: ${image_sha}
resident_runtime:
initial_login_replies: 3
code_commit_reply_verified: true
branch_move_reply_verified: true
sustained_login_replies_before_recovery: 2
recovery_control:
selected_by_native_runtime: false
per_deployment_capability_required: true
protected_relay_capability_verified: true
wrong_capability_ignored_while_native_remained_resident: true
recovery_beacon_write_read: PASS_100
boundary:
qemu_capability: 100
physical_server_capability: 0
final_native_residency_proven: false
next_action: PHYSICAL_ONE_TIME_FINAL_RESIDENCY_AND_PROTECTED_RECOVERY_GATE
EOF