Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/proto-fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
# uploaded. A crash is reported by target name only.
run: |
set -uo pipefail
targets="credstore_formats credstore_ops v3_handshake v3_pairing v3_parsers v3_record_open v3_replay_window v3_session_sim"
targets="credstore_formats credstore_ops v3_cookie v3_handshake v3_pairing v3_parsers v3_record_open v3_replay_window v3_session_sim"
listed=$(cargo "+$FUZZ_TOOLCHAIN" fuzz list | sort | tr '\n' ' ' | sed 's/ $//')
if [ "$listed" != "$targets" ]; then
echo "::error::fuzz targets are '$listed', expected '$targets'"
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 42 additions & 2 deletions host/src/transport/v3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use std::time::{Duration, Instant};
use eternal_credstore::{CredentialSnapshot, Finalize, StoreError};
use eternal_wire::v2::control::{ByeReason, ControlMessage, CONTROL_HEADER_SIZE};
use eternal_wire::v2::{CommonPrefix, PacketType};
use eternal_wire::v3::cookie::CookieMode;
use eternal_wire::v3::host::{
CloseReason, HostConfig, HostCredentials, HostOutput, HostSessions, PeerAddr,
};
Expand All @@ -61,6 +62,18 @@ impl PeerAddr for PeerId {
fn rate_bucket(&self) -> Self::Bucket {
(self.link, self.addr.map(|addr| addr.rate_bucket()))
}

/// The link, then the source's IP and port if it has one.
fn cookie_input(&self) -> Vec<u8> {
let mut out = match self.link {
LinkId::Udp => vec![0],
LinkId::Usb { device_id } => [&[1][..], &device_id.to_le_bytes()].concat(),
};
if let Some(addr) = self.addr {
out.extend(addr.cookie_input());
}
out
}
}

/// How often pending records are checked for expiry.
Expand Down Expand Up @@ -383,8 +396,32 @@ impl V3Host {
now: Instant,
wall_us: u64,
) -> Self {
Self::with_cookies(
host_id,
snapshot,
pairings,
rng,
now,
wall_us,
CookieMode::UnderLoad,
)
}

/// [`new`](Self::new), with message 1's cookies required as `cookies`
/// says on both handshakes: tests run the cookie path with `Always`.
pub(super) fn with_cookies(
host_id: Id16,
snapshot: Option<Arc<CredentialSnapshot>>,
pairings: Pairings,
rng: Box<dyn Random + Send>,
now: Instant,
wall_us: u64,
cookies: CookieMode,
) -> Self {
let mut config = host_config(host_id);
config.cookies = cookies;
Self {
sessions: HostSessions::new(host_config(host_id), rng, Duration::ZERO, wall_us),
sessions: HostSessions::new(config, rng, Duration::ZERO, wall_us),
creds: Credentials {
snapshot: snapshot.unwrap_or_default(),
accept: Accept::default(),
Expand All @@ -395,7 +432,10 @@ impl V3Host {
streaming: None,
outbox: VecDeque::new(),
pairing: PairingHost::new(
PairingConfig::default(),
PairingConfig {
cookies,
..PairingConfig::default()
},
host_id,
env!("CARGO_PKG_VERSION"),
"",
Expand Down
57 changes: 52 additions & 5 deletions host/src/transport/v3/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,21 @@ impl Rig {
}

fn with(store: Store, live: Live) -> Self {
Self::with_cookies(store, live, CookieMode::UnderLoad)
}

fn with_cookies(store: Store, live: Live, cookies: CookieMode) -> Self {
let start = Instant::now();
let mut session = Session::new(100);
session.require_v3();
let host = V3Host::new(
let host = V3Host::with_cookies(
HOST_ID,
Some(store.snapshot()),
Pairings::Ready,
Box::new(SeededRandom::new(9)),
start,
WALL_S * 1_000_000,
cookies,
);
Self {
host,
Expand Down Expand Up @@ -320,6 +325,19 @@ fn to_pad(rig: &mut Rig, pad: &mut Pad, sends: Vec<(PeerId, Vec<u8>)>) {
settle(rig, pad, outputs);
}

/// Sends a pairing message 1 and gives back what answers it, after the
/// cookie round trip if the host asks for one.
fn pair_msg1(rig: &mut Rig, from: SocketAddr, ipad: &mut PairingClient) -> Vec<(PeerId, Vec<u8>)> {
let first = rig.receive(from, ipad.msg1());
match first.as_slice() {
[(_, reply)] => match ipad.handle_cookie(reply) {
Some(msg1) => rig.receive(from, &msg1),
None => first,
},
_ => first,
}
}

/// Moves time on in steps, ticking both sides like the real loops do.
fn advance(rig: &mut Rig, pad: &mut Pad, by: Duration) {
let end = rig.now + by;
Expand Down Expand Up @@ -1071,17 +1089,31 @@ fn liveness_ends_the_v3_session_with_a_sealed_goodbye() {
///
/// With `in_commit` the crash points are those of the commit itself;
/// otherwise the session commits first and `job` is killed.
/// Each crash point runs with and without message 1's cookie round trip.
fn crash_each_step(
records: &[(u8, u8, Option<u8>, bool, u64)],
dev: u8,
in_commit: bool,
job: fn(&mut Rig, &mut Pad),
check: fn(&Store, bool),
) {
for cookies in [CookieMode::UnderLoad, CookieMode::Always] {
crash_each_step_with(records, dev, in_commit, job, check, cookies);
}
}

fn crash_each_step_with(
records: &[(u8, u8, Option<u8>, bool, u64)],
dev: u8,
in_commit: bool,
job: fn(&mut Rig, &mut Pad),
check: fn(&Store, bool),
cookies: CookieMode,
) {
let world = world(records);
for k in 0.. {
let (store, _, live) = world.open();
let mut rig = Rig::with(store, live);
let mut rig = Rig::with_cookies(store, live, cookies);
let mut pad = Pad::new(50_000);
if !in_commit {
connect(&mut rig, &mut pad, dev);
Expand All @@ -1094,6 +1126,11 @@ fn crash_each_step(
job(&mut rig, &mut pad);
let crashed = rig.live.steps.count.get() > base + k;
rig.live.steps.crash_at.set(None);
assert_eq!(
rig.host.table().cookie_replies() > 0,
cookies == CookieMode::Always,
"the cookie path ran only when required"
);
check_invariants(rig.store());
check(rig.store(), crashed);
assert_eq!(rig.host.held_invalid(), 0, "every job released its holds");
Expand Down Expand Up @@ -1617,7 +1654,7 @@ fn pair(
&mut SeededRandom::new(u64::from(slot) + 40),
)
.unwrap();
let msg2 = rig.receive(from, ipad.msg1());
let msg2 = pair_msg1(rig, from, &mut ipad);
assert_eq!(msg2.len(), 1, "message 2");
let accept = ipad.read_msg2(&msg2[0].1).unwrap();
let key = ClientKey {
Expand Down Expand Up @@ -1770,10 +1807,16 @@ fn pairings_that_are_unavailable_open_no_window() {
/// went out only if it is, and the iPad's next confirm finishes the job.
#[test]
fn a_crash_in_the_pairing_write_never_answers_what_isnt_stored() {
for cookies in [CookieMode::UnderLoad, CookieMode::Always] {
crash_in_the_pairing_write(cookies);
}
}

fn crash_in_the_pairing_write(cookies: CookieMode) {
for k in 0.. {
let world = world(&[]);
let (store, _, live) = world.open();
let mut rig = Rig::with(store, live);
let mut rig = Rig::with_cookies(store, live, cookies);
let pad = Pad::new(50_000);
let at = rig.at();
let qr = rig.host.open_pairing(at, host_addr(), "PC").unwrap();
Expand All @@ -1786,9 +1829,13 @@ fn a_crash_in_the_pairing_write_never_answers_what_isnt_stored() {
&mut SeededRandom::new(3),
)
.unwrap();
let msg2 = rig.receive(pad.addr, ipad.msg1());
let msg2 = pair_msg1(&mut rig, pad.addr, &mut ipad);
let accept = ipad.read_msg2(&msg2[0].1).unwrap();
let dev_id = accept.dev_id;
assert_eq!(
rig.host.pairing.cookie_replies() > 0,
cookies == CookieMode::Always
);
let base = rig.live.steps.count.get();
rig.live.steps.crash_at.set(Some(base + k));
let confirm = ipad.confirm(None).unwrap();
Expand Down
2 changes: 2 additions & 0 deletions proto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ snow = { version = "0.10", default-features = false, features = [
"risky-raw-split",
] }
ring = "0.17"
# Constant-time comparison of the cookie MAC (already in the tree via snow).
subtle = "2.6"
zeroize = "1"

[[example]]
Expand Down
1 change: 1 addition & 0 deletions proto/fuzz/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions proto/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ test = false
doc = false
bench = false

[[bin]]
name = "v3_cookie"
path = "fuzz_targets/v3_cookie.rs"
test = false
doc = false
bench = false

[[bin]]
name = "v3_session_sim"
path = "fuzz_targets/v3_session_sim.rs"
Expand Down
Loading
Loading