Skip to content
Open
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
71 changes: 61 additions & 10 deletions guest/src/sandboxfs/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -269,23 +269,60 @@ const DefaultTtls = struct {
pub const entry_ms: u64 = 1000;
};

const CreatedOwnership = struct {
ino: u64,
uid: u32,
gid: u32,
};

const SandboxFs = struct {
allocator: std.mem.Allocator,
fuse_fd: posix.fd_t,
rpc: ?fs_rpc.FsRpcClient,
map_created_ownership: bool,
created_ownership: std.ArrayList(CreatedOwnership) = .empty,
unsupported_opcode_logged: [256]bool,

pub fn init(allocator: std.mem.Allocator, fuse_fd: posix.fd_t, rpc: ?fs_rpc.FsRpcClient) SandboxFs {
pub fn init(allocator: std.mem.Allocator, fuse_fd: posix.fd_t, rpc: ?fs_rpc.FsRpcClient, map_created_ownership: bool) SandboxFs {
return .{
.allocator = allocator,
.fuse_fd = fuse_fd,
.rpc = rpc,
.map_created_ownership = map_created_ownership,
.unsupported_opcode_logged = @splat(false),
};
}

pub fn deinit(self: *SandboxFs) void {
_ = self;
self.created_ownership.deinit(self.allocator);
}

fn rememberCreatedOwnership(self: *SandboxFs, ino: u64, uid: u32, gid: u32) !void {
if (!self.map_created_ownership) return;
for (self.created_ownership.items) |*mapping| {
if (mapping.ino == ino) {
mapping.uid = uid;
mapping.gid = gid;
return;
}
}
try self.created_ownership.append(self.allocator, .{ .ino = ino, .uid = uid, .gid = gid });
}

fn applyCreatedOwnership(self: *SandboxFs, attr: *FuseAttr) void {
if (!self.map_created_ownership) return;
for (self.created_ownership.items) |mapping| {
if (mapping.ino == attr.ino) {
attr.uid = mapping.uid;
attr.gid = mapping.gid;
return;
}
}
}

fn applyCreatedOwnershipToEntry(self: *SandboxFs, entry: *ParsedEntry) void {
self.applyCreatedOwnership(&entry.attr);
entry.out.attr = entry.attr;
}

pub fn run(self: *SandboxFs) !void {
Expand Down Expand Up @@ -402,7 +439,8 @@ const SandboxFs = struct {
const res_map = response.res orelse return error.InvalidResponse;
const entry_val = cbor.getMapValue(res_map, "entry") orelse return error.InvalidResponse;
const entry_map = try expectMap(entry_val);
const entry = try parseEntry(entry_map, DefaultTtls.attr_ms, DefaultTtls.entry_ms);
var entry = try parseEntry(entry_map, DefaultTtls.attr_ms, DefaultTtls.entry_ms);
self.applyCreatedOwnershipToEntry(&entry);

try sendResponse(self.fuse_fd, header.unique, 0, std.mem.asBytes(&entry.out));
return;
Expand Down Expand Up @@ -432,7 +470,8 @@ const SandboxFs = struct {
const res_map = response.res orelse return error.InvalidResponse;
const attr_val = cbor.getMapValue(res_map, "attr") orelse return error.InvalidResponse;
const attr_map = try expectMap(attr_val);
const attr = try parseAttr(attr_map, header.nodeid);
var attr = try parseAttr(attr_map, header.nodeid);
self.applyCreatedOwnership(&attr);
const attr_ttl_ms = getMapU64(res_map, "attr_ttl_ms") orelse DefaultTtls.attr_ms;

const out = buildAttrOut(attr, attr_ttl_ms);
Expand Down Expand Up @@ -515,7 +554,8 @@ const SandboxFs = struct {
const res_map = attr_response.res orelse return error.InvalidResponse;
const attr_val = cbor.getMapValue(res_map, "attr") orelse return error.InvalidResponse;
const attr_map = try expectMap(attr_val);
const attr = try parseAttr(attr_map, header.nodeid);
var attr = try parseAttr(attr_map, header.nodeid);
self.applyCreatedOwnership(&attr);
const attr_ttl_ms = getMapU64(res_map, "attr_ttl_ms") orelse DefaultTtls.attr_ms;

const out = buildAttrOut(attr, attr_ttl_ms);
Expand Down Expand Up @@ -548,7 +588,8 @@ const SandboxFs = struct {
const res_map = response.res orelse return error.InvalidResponse;
const entry_val = cbor.getMapValue(res_map, "entry") orelse return error.InvalidResponse;
const entry_map = try expectMap(entry_val);
const entry = try parseEntry(entry_map, DefaultTtls.attr_ms, DefaultTtls.entry_ms);
var entry = try parseEntry(entry_map, DefaultTtls.attr_ms, DefaultTtls.entry_ms);
self.applyCreatedOwnershipToEntry(&entry);

try sendResponse(self.fuse_fd, header.unique, 0, std.mem.asBytes(&entry.out));
}
Expand Down Expand Up @@ -578,7 +619,9 @@ const SandboxFs = struct {
const res_map = response.res orelse return error.InvalidResponse;
const entry_val = cbor.getMapValue(res_map, "entry") orelse return error.InvalidResponse;
const entry_map = try expectMap(entry_val);
const entry = try parseEntry(entry_map, DefaultTtls.attr_ms, DefaultTtls.entry_ms);
var entry = try parseEntry(entry_map, DefaultTtls.attr_ms, DefaultTtls.entry_ms);
try self.rememberCreatedOwnership(entry.attr.ino, header.uid, header.gid);
self.applyCreatedOwnershipToEntry(&entry);

try sendResponse(self.fuse_fd, header.unique, 0, std.mem.asBytes(&entry.out));
}
Expand Down Expand Up @@ -699,7 +742,8 @@ const SandboxFs = struct {
const res_map = response.res orelse return error.InvalidResponse;
const entry_val = cbor.getMapValue(res_map, "entry") orelse return error.InvalidResponse;
const entry_map = try expectMap(entry_val);
const entry = try parseEntry(entry_map, DefaultTtls.attr_ms, DefaultTtls.entry_ms);
var entry = try parseEntry(entry_map, DefaultTtls.attr_ms, DefaultTtls.entry_ms);
self.applyCreatedOwnershipToEntry(&entry);

try sendResponse(self.fuse_fd, header.unique, 0, std.mem.asBytes(&entry.out));
}
Expand Down Expand Up @@ -914,7 +958,9 @@ const SandboxFs = struct {
const res_map = response.res orelse return error.InvalidResponse;
const entry_val = cbor.getMapValue(res_map, "entry") orelse return error.InvalidResponse;
const entry_map = try expectMap(entry_val);
const entry = try parseEntry(entry_map, DefaultTtls.attr_ms, DefaultTtls.entry_ms);
var entry = try parseEntry(entry_map, DefaultTtls.attr_ms, DefaultTtls.entry_ms);
try self.rememberCreatedOwnership(entry.attr.ino, header.uid, header.gid);
self.applyCreatedOwnershipToEntry(&entry);
const fh = getMapU64(res_map, "fh") orelse return error.InvalidResponse;
const open_flags = getMapU32(res_map, "open_flags") orelse 0;
var buf = std.ArrayList(u8).empty;
Expand Down Expand Up @@ -1072,6 +1118,7 @@ pub fn main(init: std.process.Init) !void {
var mount_point: []const u8 = "/data";
var rpc_path: []const u8 = "/dev/virtio-ports/virtio-fs";
var binds_file: ?[]const u8 = null;
var map_created_ownership = false;

var i: usize = 1;
while (i < args.len) : (i += 1) {
Expand All @@ -1090,6 +1137,10 @@ pub fn main(init: std.process.Init) !void {
i += 1;
continue;
}
if (std.mem.eql(u8, args[i], "--map-created-ownership")) {
map_created_ownership = true;
continue;
}
}

const fuse_fd = try posix.open("/dev/fuse", .{ .ACCMODE = .RDWR, .CLOEXEC = true }, 0);
Expand Down Expand Up @@ -1117,7 +1168,7 @@ pub fn main(init: std.process.Init) !void {
try mountFuse(allocator, fuse_fd, mount_point);
log.info("mounted sandboxfs at {s}", .{mount_point});

var sandboxfs = SandboxFs.init(allocator, fuse_fd, rpc_client);
var sandboxfs = SandboxFs.init(allocator, fuse_fd, rpc_client, map_created_ownership);
defer sandboxfs.deinit();

sandboxfs.run() catch |err| {
Expand Down
28 changes: 27 additions & 1 deletion host/bin/gondolin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ function bashUsage() {
console.log(
" --mount-memfs PATH Create memory-backed mount at path",
);
console.log(
" --map-created-ownership Report guest UID/GID for newly created entries",
);
console.log();
console.log("Network Options:");
console.log(
Expand Down Expand Up @@ -341,6 +344,9 @@ function bashUsage() {
console.log();
console.log("Examples:");
console.log(" gondolin bash --mount-hostfs /home/user/project:/workspace");
console.log(
" gondolin bash --mount-hostfs $PWD:/workspace --map-created-ownership",
);
console.log(
" gondolin bash --mount-hostfs /data:/data:ro --mount-memfs /tmp",
);
Expand Down Expand Up @@ -419,6 +425,9 @@ function execUsage() {
console.log(
" --mount-memfs PATH Create memory-backed mount at path",
);
console.log(
" --map-created-ownership Report guest UID/GID for newly created entries",
);
console.log(
" --image IMAGE Guest image selector (asset dir, build id, or name:tag)",
);
Expand Down Expand Up @@ -493,6 +502,7 @@ type SshCredentialSpec = {
type CommonOptions = {
mounts: MountSpec[];
memoryMounts: string[];
mapCreatedOwnership?: boolean;
allowedHosts: string[];
secrets: SecretSpec[];

Expand Down Expand Up @@ -1074,7 +1084,15 @@ function buildVmOptions(common: CommonOptions) {
: undefined;

const vmOptions: any = {
vfs: Object.keys(mounts).length > 0 ? { mounts } : undefined,
vfs:
Object.keys(mounts).length > 0 || common.mapCreatedOwnership
? {
...(Object.keys(mounts).length > 0 ? { mounts } : {}),
...(common.mapCreatedOwnership
? { mapCreatedOwnership: true }
: {}),
}
: undefined,
httpHooks,
dns,
ssh:
Expand Down Expand Up @@ -1181,6 +1199,10 @@ function parseExecArgs(argv: string[]): ExecArgs {
args.common.memoryMounts.push(path);
return i;
}
case "--map-created-ownership": {
args.common.mapCreatedOwnership = true;
return i;
}
case "--image": {
const image = optionArgs[++i];
if (!image) fail("--image requires an argument");
Expand Down Expand Up @@ -1670,6 +1692,10 @@ function parseBashArgs(argv: string[]): BashArgs {
args.memoryMounts.push(path);
break;
}
case "--map-created-ownership": {
args.mapCreatedOwnership = true;
break;
}
case "--image": {
const image = argv[++i];
if (!image) {
Expand Down
25 changes: 24 additions & 1 deletion host/src/alpine/init-scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,25 @@ if modprobe fuse > /dev/null 2>&1; then
log "[init] loaded fuse"
fi

sandboxfs_mount="/data"
sandboxfs_binds=""
sandboxfs_map_created_ownership=""

if [ -r /proc/cmdline ]; then
for arg in $(cat /proc/cmdline); do
case "\${arg}" in
sandboxfs.mount=*)
sandboxfs_mount="\${arg#sandboxfs.mount=}"
;;
sandboxfs.bind=*)
sandboxfs_binds="\${arg#sandboxfs.bind=}"
;;
sandboxfs.map-created-ownership=*)
sandboxfs_map_created_ownership="\${arg#sandboxfs.map-created-ownership=}"
;;
esac
done
fi
wait_for_sandboxfs() {
for i in $(seq 1 300); do
if grep -q " \${sandboxfs_mount} fuse.sandboxfs " /proc/mounts; then
Expand Down Expand Up @@ -296,7 +315,11 @@ if [ -x /usr/bin/sandboxfs ]; then
log "[init] sandboxfs rpc path \${sandboxfs_rpc_path}"
sandboxfs_binds_file="/run/sandboxfs.binds"
rm -f "\${sandboxfs_binds_file}"
/usr/bin/sandboxfs --mount "\${sandboxfs_mount}" --rpc-path "\${sandboxfs_rpc_path}" --binds-file "\${sandboxfs_binds_file}" > "\${SANDBOXFS_LOG}" 2>&1 &
sandboxfs_ownership_arg=""
if [ "\${sandboxfs_map_created_ownership}" = "1" ]; then
sandboxfs_ownership_arg="--map-created-ownership"
fi
/usr/bin/sandboxfs --mount "\${sandboxfs_mount}" --rpc-path "\${sandboxfs_rpc_path}" --binds-file "\${sandboxfs_binds_file}" \${sandboxfs_ownership_arg} > "\${SANDBOXFS_LOG}" 2>&1 &

if wait_for_sandboxfs; then
sandboxfs_ready=1
Expand Down
6 changes: 6 additions & 0 deletions host/src/build/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,12 @@ export async function resolveSandboxBinaryPaths(
}
}

if (envFlagEnabled(BUILD_SANDBOX_HELPERS_FROM_SOURCE_ENV)) {
const paths = await buildSandboxBinaryPathsFromSource(config.arch, log);
assertSandboxBinaryPathsExist(paths);
return paths;
}

if (options.skipBinaries) {
const paths = resolveExistingGuestZigOutBinaryPaths();
assertSandboxBinaryPathsExist(paths);
Expand Down
4 changes: 3 additions & 1 deletion host/src/sandbox/control-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* - pty_resize { type: "pty_resize", id, rows, cols }
* - lifecycle { type: "lifecycle", action: "restart" | "shutdown" }
* - snapshot { type: "snapshot", id, path }
* - boot { type: "boot", fuseMount?, fuseBinds? }
* - boot { type: "boot", fuseMount?, fuseBinds?, mapCreatedOwnership? }
*
* Server → Client:
* - status { type: "status", state: "starting" | "running" | "stopped" }
Expand Down Expand Up @@ -88,6 +88,8 @@ export type BootCommandMessage = {
fuseMount?: string;
/** guest paths to bind into the fuse mount */
fuseBinds?: string[];
/** ownership view for entries created by guest processes */
mapCreatedOwnership?: boolean;
};

export type ExecWindowCommandMessage = {
Expand Down
23 changes: 17 additions & 6 deletions host/src/sandbox/server-boot-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { BootCommandMessage } from "./control-protocol.ts";
export type SandboxFsConfig = {
fuseMount: string;
fuseBinds: string[];
mapCreatedOwnership: boolean;
};

export function normalizeSandboxFsConfig(
Expand All @@ -15,9 +16,14 @@ export function normalizeSandboxFsConfig(
"fuseMount",
);
const fuseBinds = normalizeBindList(message.fuseBinds ?? []);
const mapCreatedOwnership = message.mapCreatedOwnership ?? false;
if (typeof mapCreatedOwnership !== "boolean") {
throw new Error("mapCreatedOwnership must be a boolean");
}
return {
fuseMount,
fuseBinds,
mapCreatedOwnership,
};
}

Expand Down Expand Up @@ -66,7 +72,7 @@ export function isSameSandboxFsConfig(
for (let i = 0; i < left.fuseBinds.length; i += 1) {
if (left.fuseBinds[i] !== right.fuseBinds[i]) return false;
}
return true;
return left.mapCreatedOwnership === right.mapCreatedOwnership;
}

/**
Expand Down Expand Up @@ -111,11 +117,16 @@ export function buildSandboxfsAppend(
binds.push(bind);
length += extra;
}

if (binds.length === 0) return base;
// parents have to be bound before their children
binds.sort();
return `${base}${prefix}${binds.join(",")}`;
const pieces = [base];
if (binds.length > 0) {
// parents have to be bound before their children
binds.sort();
pieces.push(`${prefix}${binds.join(",")}`);
}
if (config.mapCreatedOwnership) {
pieces.push("sandboxfs.map-created-ownership=1");
}
return joinCmdline(pieces);
}

/** Build the result of the sandboxfs `mounts` rpc op */
Expand Down
3 changes: 3 additions & 0 deletions host/src/vm/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ export class VM {
private vfs: SandboxVfsProvider | null;
private readonly fuseMount: string;
private readonly fuseBinds: string[];
private readonly mapCreatedOwnership: boolean;
private readonly shortcutBindMounts: string[];
private bootSent = false;
private vfsReadyPromise: Promise<void> | null = null;
Expand Down Expand Up @@ -413,6 +414,7 @@ export class VM {
let fuseConfig = resolveFuseConfig(options.vfs, fuseMounts);
this.fuseMount = fuseConfig.fuseMount;
this.fuseBinds = fuseConfig.fuseBinds;
this.mapCreatedOwnership = options.vfs?.mapCreatedOwnership ?? false;

const sandboxOptions: SandboxServerOptions = { ...options.sandbox };
if (sandboxOptions.vfsProvider && options.vfs) {
Expand Down Expand Up @@ -1602,6 +1604,7 @@ fi
type: "boot",
fuseMount: this.fuseMount,
fuseBinds: this.fuseBinds,
mapCreatedOwnership: this.mapCreatedOwnership,
});
}

Expand Down
2 changes: 2 additions & 0 deletions host/src/vm/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export type VmVfsOptions = {
hooks?: VfsHooks;
/** guest path for the fuse mount (default: "/data") */
fuseMount?: string;
/** report creating guest process UID/GID for newly created entries (default: false) */
mapCreatedOwnership?: boolean;
};

export type VmRootfsOptions = {
Expand Down
Loading
Loading