mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Remote, scripts] (P7): name every single-session first-frame outcome and pin its token-first order
This commit is contained in:
@@ -240,9 +240,36 @@ void CloseHandoffRefusingQueued(int handoff) {
|
||||
std::fflush(nullptr);
|
||||
::_exit(0);
|
||||
}
|
||||
if (received != MOBILEGL_ERR_BUFFER_TOO_SMALL) ::_exit(0);
|
||||
helloFrame.resize(static_cast<std::size_t>(bytes));
|
||||
if (control->ReceiveFrame({helloFrame.data(), helloFrame.size()}, &bytes, 0) != MOBILEGL_OK) ::_exit(0);
|
||||
if (received == MOBILEGL_ERR_TRANSPORT_CLOSED) {
|
||||
// Fix round: this used to be a bare `_exit(0)` whatever had arrived. A close INSIDE
|
||||
// the frame is answered by name, as the TCP supervisor answers it (the peer may have
|
||||
// closed only its write side); a close before any byte has nobody to answer, and the
|
||||
// log says which of the two it was.
|
||||
if (control->BufferedBytes() != 0)
|
||||
Refuse(*control, Protocol::RefuseCode::MalformedHello, Server::kFirstFrameTruncated);
|
||||
else
|
||||
WireLogError("MG_Remote server: pid=%d control peer closed before sending a first frame", selfPid);
|
||||
std::fflush(nullptr);
|
||||
::_exit(0);
|
||||
}
|
||||
if (received == MOBILEGL_ERR_BUFFER_TOO_SMALL) {
|
||||
// The reassembler holds the whole frame already; this second call copies it out.
|
||||
helloFrame.resize(static_cast<std::size_t>(bytes));
|
||||
if (control->ReceiveFrame({helloFrame.data(), helloFrame.size()}, &bytes, 0) != MOBILEGL_OK) {
|
||||
WireLogError("MG_Remote server: pid=%d first frame of %llu bytes could not be copied out", selfPid,
|
||||
static_cast<unsigned long long>(helloFrame.size()));
|
||||
std::fflush(nullptr);
|
||||
::_exit(0);
|
||||
}
|
||||
} else if (received != MOBILEGL_OK) {
|
||||
WireLogError("MG_Remote server: pid=%d first frame could not be read (rc=%d)", selfPid,
|
||||
static_cast<int>(received));
|
||||
std::fflush(nullptr);
|
||||
::_exit(0);
|
||||
}
|
||||
// MOBILEGL_OK with nothing copied is a ZERO-LENGTH frame. It was the one complete frame
|
||||
// this read exited on without a word (fix round); `helloFrame` stays empty and the
|
||||
// classification below names it, as the TCP supervisor's does ("no identifier").
|
||||
}
|
||||
// THE FIRST FRAME, CLASSIFIED BY THE SAME FUNCTION THE TCP SUPERVISOR USES (plan §1.1, fuzz
|
||||
// arm 1 row: "ServerMain.cpp:193 CtrlEnvelopeBufferHasIdentifier"). The identifier is asked
|
||||
|
||||
Executable → Regular
+39
-6
@@ -33,8 +33,14 @@ Five malformations, delivered at the two places a control frame is read:
|
||||
(the truncated frame and the wrong union type) ended the session without a word.
|
||||
|
||||
(3) SINGLE-SESSION SHAPE (no --serve), where the session process reads its own first frame
|
||||
through the same classification: a wrong identifier and a silent peer are each answered by
|
||||
name and the process exits 0, where it used to `_exit(67)` without a word.
|
||||
through the same classification: a wrong identifier, a zero-length frame, a truncated header
|
||||
and a silent peer are each answered by name and the process exits 0, where it used to
|
||||
`_exit(67)` (or `_exit(0)`) without a word. And the one place RunSession's own token-first
|
||||
order is reachable: an unauthenticated Hello with a wrong layout or wire major is answered
|
||||
Refuse{Authentication} with nothing of ours in it.
|
||||
|
||||
The first-frame phase also carries the two shapes the f2-auth fix round added: a zero-length
|
||||
frame, and a DataBind whose nonce is not 16 bytes (the supervisor's own named refusal).
|
||||
|
||||
The supervisor runs with the authentication backoff OFF (MOBILEGL_IPC_AUTH_BACKOFF_AFTER=0): every
|
||||
malformation above is an authentication failure from 127.0.0.1, and with the default threshold the
|
||||
@@ -171,11 +177,15 @@ def main():
|
||||
'wrong_union_type': (8, smoke.kDetailNotAHello),
|
||||
'bad_magic': (8, smoke.kDetailNotAFrame),
|
||||
'null_hello_table': (8, smoke.kDetailNotAHello),
|
||||
'zero_length': (8, smoke.kDetailNoIdentifier),
|
||||
'short_nonce_data_bind': (8, smoke.kDetailBadDataBind),
|
||||
'silent': (4, smoke.kDetailDeadline),
|
||||
}
|
||||
cases = dict(malformations(schema, flatbuffers, fingerprint))
|
||||
cases['bad_magic'] = (b'XXXX' + bytes(60), False)
|
||||
cases['null_hello_table'] = (envelope(schema, flatbuffers, schema['CtrlMsg'].CtrlMsg.Hello), False)
|
||||
cases['zero_length'] = (framed(b''), False)
|
||||
cases['short_nonce_data_bind'] = (smoke.data_bind(schema, flatbuffers, os.urandom(8)), False)
|
||||
cases['silent'] = (b'', False)
|
||||
reaped_before = reap_lines(serverLog)
|
||||
for name, (payload, half_close) in cases.items():
|
||||
@@ -253,20 +263,43 @@ def main():
|
||||
# itself (the unix endpoint's children do the same), through the same classification.
|
||||
# That read used to `_exit(67)` on every malformation without a word; now the peer is
|
||||
# answered by name and the process exits 0 - the malformation is the peer's, not a fault.
|
||||
#
|
||||
# f2-auth fix round, two more kinds of case here:
|
||||
# - zero_length / truncated_length_prefix: the two first frames this read still ended on
|
||||
# with a bare `_exit(0)` - a complete frame of length 0, and a close inside the header.
|
||||
# RED before the fix: RuntimeError('control channel closed before a complete frame').
|
||||
# - unauthenticated_*: THE TOKEN IS ASKED BEFORE THE WIRE HERE TOO (PH-7 (5), ph-f.md
|
||||
# §6.4 (b)). On TCP --serve the supervisor refuses these before any fork, so the
|
||||
# supervisor smoke's unauthenticated controls never reach RunSession's own order; this
|
||||
# shape is the one that does (and the unix endpoint runs the same lines). A Hello with a
|
||||
# wrong token AND fingerprint 0 (or wire major 99) must be answered
|
||||
# Refuse{Authentication} carrying nothing of ours. RED with ValidatePeerHandshake moved
|
||||
# back before AuthenticatePeerToken: code 2 with our fingerprint in `expected` (code 1
|
||||
# with our version).
|
||||
evidence['single_session'] = {}
|
||||
for name, payload, code, detail in (
|
||||
for name, payload, half_close, code, detail in (
|
||||
('bad_identifier', smoke.hello(schema, flatbuffers, fingerprint=fingerprint, identifier=b'XXXX'),
|
||||
8, smoke.kDetailNoIdentifier),
|
||||
('silent', b'', 4, smoke.kDetailDeadline)):
|
||||
False, 8, smoke.kDetailNoIdentifier),
|
||||
('zero_length', framed(b''), False, 8, smoke.kDetailNoIdentifier),
|
||||
('truncated_length_prefix', b'MGLF\x10', True, 8, smoke.kDetailTruncated),
|
||||
('unauthenticated_wrong_layout', smoke.hello(schema, flatbuffers, fingerprint=0, token='wrong-token'),
|
||||
False, 4, 'token mismatch'),
|
||||
('unauthenticated_wire_major_99',
|
||||
smoke.hello(schema, flatbuffers, fingerprint=fingerprint, major=99, token='wrong-token'),
|
||||
False, 4, 'token mismatch'),
|
||||
('silent', b'', False, 4, smoke.kDetailDeadline)):
|
||||
with smoke.supervisor(args.server, args.out / f'single-session-{name}.log', serve=False,
|
||||
extra_env=knobs) as (port, process, serverLog):
|
||||
answer = send_first_frame(port, schema, payload, False)
|
||||
answer = send_first_frame(port, schema, payload, half_close)
|
||||
try:
|
||||
exited = process.wait(timeout=10)
|
||||
except Exception: # noqa: BLE001 - still running is the red shape here
|
||||
exited = None
|
||||
assert answer.get('code') == code and answer.get('detail') == detail, (
|
||||
f'single-session {name}: expected Refuse code {code} "{detail}", got {answer}; see {serverLog}')
|
||||
assert answer.get('expected') == 0, (
|
||||
f'single-session {name}: the refusal carried {answer.get("expected")} in `expected`; '
|
||||
f'our fingerprint is {fingerprint}')
|
||||
assert exited == 0, f'single-session {name}: the session process exited {exited}, not 0'
|
||||
evidence['single_session'][name] = {'refusal': answer, 'exit': exited}
|
||||
(args.out / 'fuzz-control-frames.json').write_text(json.dumps(evidence, indent=2))
|
||||
|
||||
@@ -364,6 +364,7 @@ kDetailTruncated = 'first frame ended before it was complete'
|
||||
kDetailNoIdentifier = 'first frame carries no CtrlEnvelope identifier'
|
||||
kDetailUnverifiable = 'first frame did not verify as a CtrlEnvelope'
|
||||
kDetailNotAHello = 'first control frame is not a verifiable Hello'
|
||||
kDetailBadDataBind = 'first frame is a DataBind without a 16-byte nonce'
|
||||
kDetailDeadline = 'no authenticated first frame within the pre-auth deadline'
|
||||
kDetailPendingFull = 'too many connections are awaiting authentication'
|
||||
kDetailDisplaced = 'displaced from the pre-auth queue by a connection from another address'
|
||||
|
||||
Reference in New Issue
Block a user