Skip to content

Commit 8abee62

Browse files
committed
sandlock-oci: shorten supervisor socket path to fit sun_path under CRI
Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent 76e4b4f commit 8abee62

2 files changed

Lines changed: 60 additions & 10 deletions

File tree

crates/sandlock-oci/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ pub mod supervisor;
1818

1919
pub use policy::OciPolicy;
2020
pub use state::{SandboxState, ExitInfo, Status};
21-
pub use supervisor::{SupervisorCmd, SupervisorReply, SUPERVISOR_SOCKET};
21+
pub use supervisor::{SupervisorCmd, SupervisorReply};

crates/sandlock-oci/src/supervisor.rs

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,6 @@ fn exit_info_from_resp(resp: Option<Resp>) -> Option<crate::state::ExitInfo> {
161161
}
162162
}
163163

164-
/// Filename of the supervisor control socket inside the sandbox state dir.
165-
pub const SUPERVISOR_SOCKET: &str = "supervisor.sock";
166-
167164
/// Commands the CLI sends to the Supervisor over the Unix socket.
168165
#[derive(Debug, serde::Deserialize, serde::Serialize)]
169166
#[serde(tag = "cmd", rename_all = "lowercase")]
@@ -204,11 +201,29 @@ pub enum SupervisorReply {
204201
Exit { code: Option<i32>, signal: Option<i32> },
205202
}
206203

204+
/// Deterministic 64-bit FNV-1a hash of `id` as 16 lowercase hex chars.
205+
///
206+
/// Keeps the supervisor socket path short enough for `sockaddr_un.sun_path`
207+
/// (108 bytes incl. NUL) even when the runtime root and container id are long
208+
/// (containerd passes a 64-char id under /run/containerd/runc/<ns>). Only needs
209+
/// to be stable within a single binary: bind and connect run the same build.
210+
fn fnv1a_hex(id: &str) -> String {
211+
const OFFSET: u64 = 0xcbf29ce484222325;
212+
const PRIME: u64 = 0x0000_0100_0000_01b3;
213+
let mut h = OFFSET;
214+
for b in id.as_bytes() {
215+
h ^= *b as u64;
216+
h = h.wrapping_mul(PRIME);
217+
}
218+
format!("{:016x}", h)
219+
}
220+
207221
/// Returns the path to the supervisor socket for the given sandbox ID.
222+
///
223+
/// Lives directly under the state dir as `<fnv16(id)>.sock` (not under the
224+
/// per-id state subdir) so the path stays well under the `sun_path` limit.
208225
pub fn socket_path(id: &str) -> PathBuf {
209-
PathBuf::from(crate::state::state_dir())
210-
.join(id)
211-
.join(SUPERVISOR_SOCKET)
226+
PathBuf::from(crate::state::state_dir()).join(format!("{}.sock", fnv1a_hex(id)))
212227
}
213228

214229
/// Send a command to a running supervisor and return its reply (blocking).
@@ -315,6 +330,13 @@ async fn supervisor_main(
315330
}
316331
};
317332

333+
// Restrict connects to the owner (root, same as the runtime). Best-effort:
334+
// the path-length fix is what matters; a chmod failure must not abort create.
335+
{
336+
use std::os::unix::fs::PermissionsExt;
337+
let _ = std::fs::set_permissions(&sock_path, std::fs::Permissions::from_mode(0o700));
338+
}
339+
318340
// Set up the control channel. The child end is mapped onto CONTROL_FD inside
319341
// the confined process; the daemon keeps the other end to drive
320342
// RunMain/RunExec/Shutdown.
@@ -976,6 +998,13 @@ async fn supervisor_restore_main(
976998
}
977999
};
9781000

1001+
// Restrict connects to the owner (root, same as the runtime). Best-effort:
1002+
// the path-length fix is what matters; a chmod failure must not abort restore.
1003+
{
1004+
use std::os::unix::fs::PermissionsExt;
1005+
let _ = std::fs::set_permissions(&sock_path, std::fs::Permissions::from_mode(0o700));
1006+
}
1007+
9791008
// Restore: forks the child under the saved policy, injects the checkpoint,
9801009
// and RESUMES it. The child is already running on return — there is no
9811010
// separate start step.
@@ -1026,10 +1055,31 @@ mod tests {
10261055
use super::*;
10271056

10281057
#[test]
1029-
fn socket_path_is_under_state_dir() {
1058+
fn socket_path_uses_short_hashed_name_under_state_dir() {
10301059
let p = socket_path("my-sandbox");
1031-
assert!(p.to_str().unwrap().contains("my-sandbox"));
1032-
assert!(p.to_str().unwrap().contains("supervisor.sock"));
1060+
let s = p.to_str().unwrap();
1061+
assert!(s.starts_with(&crate::state::state_dir()));
1062+
assert!(s.ends_with(".sock"));
1063+
// file name is 16 hex chars + ".sock" = 21 bytes, never the raw id.
1064+
assert_eq!(p.file_name().unwrap().to_str().unwrap().len(), 21);
1065+
assert!(!s.contains("my-sandbox"));
1066+
}
1067+
1068+
#[test]
1069+
fn socket_filename_keeps_path_under_sun_len_for_cri_root() {
1070+
// containerd's runc-v2 shim passes this root plus a 64-char id.
1071+
let cri_root = "/run/containerd/runc/k8s.io";
1072+
let id = "a".repeat(64);
1073+
let full = format!("{}/{}.sock", cri_root, fnv1a_hex(&id));
1074+
assert!(full.len() < 108, "socket path too long: {} bytes", full.len());
1075+
}
1076+
1077+
#[test]
1078+
fn fnv1a_hex_is_deterministic_and_distinct() {
1079+
assert_eq!(fnv1a_hex("abc"), fnv1a_hex("abc"));
1080+
assert_ne!(fnv1a_hex("abc"), fnv1a_hex("abd"));
1081+
assert_eq!(fnv1a_hex("abc").len(), 16);
1082+
assert!(fnv1a_hex("abc").chars().all(|c| c.is_ascii_hexdigit()));
10331083
}
10341084

10351085
#[test]

0 commit comments

Comments
 (0)