xtask/tests/ssh_server_advisory_floor.rs
//! **Gunnar's SSH server resolves no russh with a known advisory** (lane OSSF-NEXT,
//! 2026-10-30).
//!
//! `gunnar-ssh` is the network-facing SSH server. russh below 0.64.0 accepted a
//! publickey authentication whose signature was made with another algorithm than the
//! one offered (GHSA-j7h2-66j4-ghjf), and below 0.64.1 a crafted agent constraint
//! allocated without a bound (GHSA-4wc5-f2rc-q74m). The four russh advisories of
//! 2026-10-30 are fixed only in 0.65.0, and GHSA-fw5c-8223-6h42 hits servers too: a peer
//! that declares a zero maximum packet size stalls the channel's writes for good
//! (GHSA-4p9f-7p9m-5vfg, GHSA-9wfm-jcj8-jfv9 and GHSA-vp5q-ff62-jwqv are the client's
//! EXT_INFO and the agent server's). The test reads every committed lock
//! that builds `gunnar-ssh` and asks which russh THAT package links: a `^0.64`
//! requirement over a stale lock would still ship the old one.
//!
//! The root lock may carry a second, older russh until edda's nornir-git 0.2.3 (the
//! client's git transport, on russh 0.64.1 since edda b02925f) is on crates.io; this
//! test is about the server's russh, and the client side is the vulnerability gate's.
use std::path::{Path, PathBuf};
const FLOOR: (u64, u64, u64) = (0, 65, 0);
/// The advisories under [`FLOOR`].
const UNDER_FLOOR: &str = "GHSA-j7h2-66j4-ghjf, GHSA-4wc5-f2rc-q74m, GHSA-fw5c-8223-6h42, \
GHSA-4p9f-7p9m-5vfg, GHSA-9wfm-jcj8-jfv9, GHSA-vp5q-ff62-jwqv";
fn parse(v: &str) -> (u64, u64, u64) {
let core = v.split(['-', '+']).next().unwrap_or(v);
let mut it = core.split('.').map(|p| p.parse::<u64>().unwrap_or(0));
(
it.next().unwrap_or(0),
it.next().unwrap_or(0),
it.next().unwrap_or(0),
)
}
/// The packages of a lock: (name, version, dependency lines).
fn packages(lock: &str) -> Vec<(String, String, Vec<String>)> {
let mut out: Vec<(String, String, Vec<String>)> = Vec::new();
let mut in_deps = false;
for line in lock.lines() {
let l = line.trim();
if l == "[[package]]" {
out.push((String::new(), String::new(), Vec::new()));
in_deps = false;
} else if let Some(cur) = out.last_mut() {
if let Some(v) = l.strip_prefix("name = ") {
cur.0 = v.trim_matches('"').to_string();
} else if let Some(v) = l.strip_prefix("version = ") {
cur.1 = v.trim_matches('"').to_string();
} else if l.starts_with("dependencies = [") {
in_deps = true;
} else if in_deps && l == "]" {
in_deps = false;
} else if in_deps {
cur.2
.push(l.trim_end_matches(',').trim_matches('"').to_string());
}
}
}
out
}
/// The russh version `pkg` links in `lock`: its dependency line names it
/// (`russh` alone when the lock has one russh, `russh 0.64.1` when it has two).
fn russh_of(lock: &str, pkg: &str) -> Option<String> {
let pk = packages(lock);
let dep = pk
.iter()
.find(|p| p.0 == pkg)?
.2
.iter()
.find(|d| d.split(' ').next() == Some("russh"))?
.clone();
match dep.split(' ').nth(1) {
Some(v) => Some(v.to_string()),
None => pk.iter().find(|p| p.0 == "russh").map(|p| p.1.clone()),
}
}
fn locks_with(root: &Path, pkg: &str) -> Vec<PathBuf> {
let mut out = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(d) = stack.pop() {
let Ok(rd) = std::fs::read_dir(&d) else {
continue;
};
for e in rd.flatten() {
let p = e.path();
let n = e.file_name().to_string_lossy().into_owned();
if p.is_dir() {
if !matches!(n.as_str(), "target" | ".git" | "fixtures" | "vendor") {
stack.push(p);
}
} else if n == "Cargo.lock"
&& std::fs::read_to_string(&p)
.is_ok_and(|s| s.contains(&format!("name = \"{pkg}\"")))
{
out.push(p);
}
}
}
out.sort();
out
}
#[test]
fn the_reader_sees_which_russh_a_package_links() {
let two = "[[package]]\nname = \"russh\"\nversion = \"0.63.3\"\n\n[[package]]\nname = \"russh\"\nversion = \"0.64.1\"\n\n[[package]]\nname = \"gunnar-ssh\"\nversion = \"2.0.0\"\ndependencies = [\n \"russh 0.63.3\",\n \"tokio\",\n]\n";
assert_eq!(russh_of(two, "gunnar-ssh").as_deref(), Some("0.63.3"));
let one = "[[package]]\nname = \"russh\"\nversion = \"0.64.1\"\n\n[[package]]\nname = \"gunnar-ssh\"\nversion = \"2.0.0\"\ndependencies = [\n \"russh\",\n]\n";
assert_eq!(russh_of(one, "gunnar-ssh").as_deref(), Some("0.64.1"));
assert!(parse("0.63.3") < FLOOR && parse("0.64.1") < FLOOR && parse("0.65.0") >= FLOOR);
}
#[test]
fn every_lock_builds_the_ssh_server_on_a_russh_without_advisories() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
let locks = locks_with(&root, "gunnar-ssh");
assert!(
locks
.iter()
.any(|l| l.parent().and_then(|p| p.canonicalize().ok()) == root.canonicalize().ok()),
"the root lock builds gunnar-ssh: {locks:?}"
);
let mut bad = Vec::new();
for lock in &locks {
let text = std::fs::read_to_string(lock).unwrap();
for pkg in ["gunnar-ssh", "gunnar-client"] {
if let Some(v) = russh_of(&text, pkg) {
if parse(&v) < FLOOR {
bad.push(format!(
"{}: {pkg} links russh {v} (< 0.65.0: {UNDER_FLOOR})",
lock.display()
));
}
}
}
}
assert!(bad.is_empty(), "{}", bad.join("\n"));
}