// SPDX-License-Identifier: GPL-2.0 /* * CUBELinux: the `cube(2)` syscall — the kernel's coordinate interface. * * The store's operations live in Rust (drivers/cube/cubelinux_store.rs) and are already * proven: they append to a write-ahead log durably, replay it, fold it into the image, and * survive a torn tail. What was missing was a way for a program to *call* them. This file is * that way and nothing else. * * Why C for the entry point: syscalls are defined by `SYSCALL_DEFINE*`, which is a C macro * that registers the function in the syscall table with the right calling convention. Rust in * this kernel cannot define one, so the entry point, the user copies and the argument * validation are here, and the Rust side sees only kernel memory and validated numbers. That * split is deliberate: everything that touches a userspace pointer is in one place, and * everything that touches the store's bytes is in the other. */ #include #include #include #include #include #include #include #include /* * Implemented in Rust. The coordinate is passed as its parts rather than as a struct, so the * format knowledge on the Rust side stays in the module that owns it — including the Morton * encoding, which must produce exactly the key a userspace reader decodes. */ int cubelinux_kernel_put(const __u8 *space, __u64 x, __u64 y, __u64 z, const void *value, size_t len, __u16 flags); ssize_t cubelinux_kernel_get(const __u8 *space, __u64 x, __u64 y, __u64 z, void *buf, size_t len, __u16 *out_flags); int cubelinux_kernel_del(const __u8 *space, __u64 x, __u64 y, __u64 z); int cubelinux_kernel_sync(void); /* * The walk (CUBE_OP_ENUM / CUBE_OP_SPACES). Its argument block travels through the same syscall * but is a different struct, so these take the pieces rather than a pointer to one — and the C * side keeps owning everything that touches a userspace pointer. */ int cubelinux_kernel_enum(const __u8 *space, __u64 cursor, void *buf, size_t cap, __u64 *out_len, __u64 *out_cursor); int cubelinux_kernel_spaces(__u64 cursor, __u8 *space_out); /* * The region walk (CUBE_OP_RANGE). The box travels as its six numbers for the same reason the * coordinate does: the format knowledge stays on the Rust side, which owns the key the box has to * become. */ int cubelinux_kernel_range(const __u8 *space, __u64 lo_x, __u64 lo_y, __u64 lo_z, __u64 hi_x, __u64 hi_y, __u64 hi_z, __u64 cursor, void *buf, size_t cap, __u64 *out_len, __u64 *out_cursor); /* * The flag scan (CUBE_OP_FLAG_SCAN). The mask and its mode travel as plain numbers: which bits mean * what is a vocabulary's business, and the kernel never interprets one — it compares masks, which is * what lets a new vocabulary attach without a format change. */ int cubelinux_kernel_flag_scan(const __u8 *space, __u16 mask, __u16 mode, __u32 every_space, __u64 cursor, void *buf, size_t cap, __u64 *out_len, __u64 *out_cursor); /* The store device path, resolved from the `cube_store=` boot parameter at boot. */ const char *cubelinux_store_device(void); /* * Where the store lives. * * A fixed `/dev/vda` was honest while a virtual machine was the only place this ran; the same * driver now has to read the box's own device as well, and a constant cannot be both. So it is * a boot parameter, and the box names its device on the kernel command line: * * cube_store=/dev/nvme0n1p2 * * (`__setup` rather than `module_param_string`, deliberately. Built-in code registers module * parameters under its *object's* name — `MODULE_PARAM_PREFIX` is `KBUILD_MODNAME "."` when * MODULE is not defined — so a `module_param_string` here would answer to * `cube_syscall.store_device`, named after this file rather than after the driver. That is a * name nobody would guess and one more thing to get wrong at 3am. A `__setup` parameter is * named exactly as written.) * * This lives in C because this kernel's Rust can express only *integer* module parameters — * rust/kernel/module_param.rs implements `ModuleParam` through `ParseInt` and nothing else — * and a path is not an integer. The Rust side asks for the string; it does not store it. * * The default keeps the boot gate's shape, so the QEMU rehearsal is unchanged. */ static char store_device_path[256] = "/dev/vda"; static int __init cube_store_setup(char *str) { strscpy(store_device_path, str, sizeof(store_device_path)); return 1; } __setup("cube_store=", cube_store_setup); /* The Rust half reads the path through this; the storage stays here. */ const char *cubelinux_store_device(void) { return store_device_path; } /* * Whether the kernel should record its own boots in the store. * * Off unless asked for, and the reason is not caution: the gate method of this tree is that the * store the kernel produces is comparable, byte for byte, with the store userspace produces from the * same mutations. A record the kernel injects that the caller never asked for would turn two of * those comparisons into non-comparisons. So it is a command-line switch, beside `cube_store=`, * and the gate that proves it is the one that turns it on. * * cube_store=/var/lib/cubelinux/store.img cube_boot_record=1 * * The hook itself is not here: it lives in the Rust driver and fires at the first write of a boot, * which is named and argued where it is implemented (`BOOT_SPACE` in cubelinux_store.rs). */ static bool boot_record_enabled; static int __init cube_boot_record_setup(char *str) { boot_record_enabled = (str[0] == '1'); return 1; } __setup("cube_boot_record=", cube_boot_record_setup); bool cubelinux_boot_record_enabled(void) { return boot_record_enabled; } /* No value may be larger than this in one call. A coordinate store is not a bulk-file path; * a caller with more than this to store has more than one record to store. */ #define CUBE_MAX_VALUE (16u * 1024u * 1024u) /* The walk's buffer ceiling: the caller offers whatever it likes, up to this. */ #define CUBE_MAX_WALK (16u * 1024u * 1024u) /* * The coordinate operations — put, get, del, sync — which travel in `struct cube_args`. */ static long cube_args_op(unsigned int op, void __user *uargs) { struct cube_args args; void *buf = NULL; long ret = 0; if (copy_from_user(&args, uargs, sizeof(args))) return -EFAULT; /* * The size is the caller's, and it must be the one this kernel implements: a caller * built against a later block would otherwise have fields silently ignored. */ if (args.size != sizeof(struct cube_args)) return -EINVAL; switch (op) { case CUBE_OP_PUT: case CUBE_OP_GET: break; case CUBE_OP_DEL: case CUBE_OP_SYNC: break; default: return -EINVAL; } if (op == CUBE_OP_PUT || op == CUBE_OP_GET) { if (args.len > CUBE_MAX_VALUE) return -E2BIG; if (args.len > 0) { buf = kvmalloc(args.len, GFP_KERNEL); if (!buf) return -ENOMEM; } } switch (op) { case CUBE_OP_PUT: if (args.len > 0 && copy_from_user(buf, (void __user *)args.value, args.len)) { ret = -EFAULT; break; } ret = cubelinux_kernel_put(args.coord.space, args.coord.x, args.coord.y, args.coord.z, buf, args.len, args.flags); break; case CUBE_OP_GET: { ssize_t got; got = cubelinux_kernel_get(args.coord.space, args.coord.x, args.coord.y, args.coord.z, buf, args.len, &args.flags); if (got < 0) { ret = got; break; } if ((u64)got > args.len) { /* * Too small. Tell the caller how much it needs, so a read is two * calls at worst and never a guess. */ args.len = (u64)got; if (copy_to_user(uargs, &args, sizeof(args))) ret = -EFAULT; else ret = -ERANGE; break; } if (got > 0 && copy_to_user((void __user *)args.value, buf, got)) { ret = -EFAULT; break; } args.len = (u64)got; if (copy_to_user(uargs, &args, sizeof(args))) ret = -EFAULT; break; } case CUBE_OP_DEL: ret = cubelinux_kernel_del(args.coord.space, args.coord.x, args.coord.y, args.coord.z); break; case CUBE_OP_SYNC: ret = cubelinux_kernel_sync(); break; } kvfree(buf); return ret; } /* * The walk — enum and spaces — which travel in `struct cube_enum_args`: its own block so that the * coordinate is not both an input and an output, and so the interface can grow by getting a new * size rather than being replaced. * * A batch fills the caller's buffer and returns how much was used plus the cursor to pass next. * A record that does not fit ends the batch; a record that cannot fit in any buffer the caller * offered comes back as -ERANGE with `len` saying what it would need, exactly as a read does. So * nobody guesses a size and nobody gets half a record. */ static long cube_enum_op(unsigned int op, void __user *uargs) { struct cube_enum_args e; void *buf = NULL; long ret = 0; u64 out_len = 0, out_cursor = 0; if (copy_from_user(&e, uargs, sizeof(e))) return -EFAULT; if (e.size != sizeof(struct cube_enum_args)) return -EINVAL; if (op == CUBE_OP_SPACES) { __u8 found[32]; ret = cubelinux_kernel_spaces(e.cursor, found); /* * The driver answers with one space written and 0, or with a negative errno. A * positive value is neither, and it must not be read as either: the buffer was not * written, so a caller handed this answer copies a space nobody found. * * This arm is the only one that advances the cursor by itself. Every other walk * leaves the cursor where the caller put it, so a bogus return there ends the walk * on the client's no-progress rule. Here it would hand the walk a cursor that keeps * moving over a space that is not there — which is not a hypothetical: a kernel error * code that had lost its sign did exactly that, and the walk served ~200,000 invented * records a minute until the machine stopped making progress. The cause is fixed * where it was (the driver's errno conversions, cubelinux_store.rs); this is the * bound that keeps a defect of that shape from becoming a loop again. */ if (ret < 0) return ret; if (ret != 0) return -EINVAL; memcpy(e.space, found, sizeof(found)); /* An index here, not a count of records: hand back the one after this space. */ e.cursor = e.cursor + 1; e.len = 0; if (copy_to_user(uargs, &e, sizeof(e))) return -EFAULT; return 0; } if (op != CUBE_OP_ENUM) return -EINVAL; if (e.len > CUBE_MAX_WALK) return -E2BIG; if (e.len > 0) { buf = kvmalloc(e.len, GFP_KERNEL); if (!buf) return -ENOMEM; } ret = cubelinux_kernel_enum(e.space, e.cursor, buf, e.len, &out_len, &out_cursor); if (ret == 0) { if (out_len > 0 && copy_to_user((void __user *)e.value, buf, out_len)) ret = -EFAULT; e.len = out_len; e.cursor = out_cursor; if (copy_to_user(uargs, &e, sizeof(e))) ret = -EFAULT; } else if (ret == -ERANGE) { /* Nothing was written; `len` now says how much one record needs. */ e.len = out_len; if (copy_to_user(uargs, &e, sizeof(e))) ret = -EFAULT; } kvfree(buf); return ret; } /* * The region walk — CUBE_OP_RANGE — which travels in `struct cube_range_args`. * * Deliberately the same shape as the space walk above, because it is the same contract with one * more input: a batch fills the caller's buffer and returns how much was used plus the cursor to * pass next; a record that does not fit ends the batch; a record that cannot fit in any buffer the * caller offered comes back as -ERANGE with `len` saying what it would need. Nobody guesses a size * and nobody gets half a record. * * The two things a caller must know beyond the walk's rules: the cursor counts the records **in the * box** rather than the records of the space (those are the records being returned), and the kernel * may *examine* more records than it returns, because it seeks on the box's key span and the span * is a bound rather than the set. That over-coverage is the honest cost of the seek, not a defect. */ static long cube_range_op(void __user *uargs) { struct cube_range_args r; void *buf = NULL; long ret = 0; u64 out_len = 0, out_cursor = 0; if (copy_from_user(&r, uargs, sizeof(r))) return -EFAULT; if (r.size != sizeof(struct cube_range_args) || r.op != CUBE_OP_RANGE) return -EINVAL; /* * An inverted box is empty, not an error: there is nothing in it, and saying so is the honest * answer. Answering it here also keeps the empty case away from the seek, where an inverted * span would be a range whose start is above its end. */ if (r.lo[0] > r.hi[0] || r.lo[1] > r.hi[1] || r.lo[2] > r.hi[2]) { r.len = 0; if (copy_to_user(uargs, &r, sizeof(r))) return -EFAULT; return 0; } if (r.len > CUBE_MAX_WALK) return -E2BIG; if (r.len > 0) { buf = kvmalloc(r.len, GFP_KERNEL); if (!buf) return -ENOMEM; } ret = cubelinux_kernel_range(r.space, r.lo[0], r.lo[1], r.lo[2], r.hi[0], r.hi[1], r.hi[2], r.cursor, buf, r.len, &out_len, &out_cursor); if (ret == 0) { if (out_len > 0 && copy_to_user((void __user *)r.value, buf, out_len)) ret = -EFAULT; r.len = out_len; r.cursor = out_cursor; if (copy_to_user(uargs, &r, sizeof(r))) ret = -EFAULT; } else if (ret == -ERANGE) { /* Nothing was written; `len` now says how much one record needs. */ r.len = out_len; if (copy_to_user(uargs, &r, sizeof(r))) ret = -EFAULT; } kvfree(buf); return ret; } /* * The flag scan — CUBE_OP_FLAG_SCAN — which travels in `struct cube_flag_scan_args`. * * The walks' shape again, because it is the walks' contract: a batch fills the caller's buffer and * returns how much was used plus the cursor to pass next; a record that does not fit ends the * batch; a record that cannot fit in any buffer the caller offered comes back as -ERANGE with `len` * saying what it would need. * * The one thing a caller must know beyond the walk's rules: the cursor counts the records that * **matched**, not the records examined, because the records the mask rejected are not answers. */ static long cube_flag_scan_op(void __user *uargs) { struct cube_flag_scan_args f; void *buf = NULL; long ret = 0; u64 out_len = 0, out_cursor = 0; if (copy_from_user(&f, uargs, sizeof(f))) return -EFAULT; if (f.size != sizeof(struct cube_flag_scan_args) || f.op != CUBE_OP_FLAG_SCAN) return -EINVAL; if (f.mode != CUBE_FLAG_ANY && f.mode != CUBE_FLAG_ALL) return -EINVAL; if (f.every_space != CUBE_SPACE_ONE && f.every_space != CUBE_SPACE_EVERY) return -EINVAL; if (f.len > CUBE_MAX_WALK) return -E2BIG; if (f.len > 0) { buf = kvmalloc(f.len, GFP_KERNEL); if (!buf) return -ENOMEM; } ret = cubelinux_kernel_flag_scan(f.space, f.mask, f.mode, f.every_space, f.cursor, buf, f.len, &out_len, &out_cursor); if (ret == 0) { if (out_len > 0 && copy_to_user((void __user *)f.value, buf, out_len)) ret = -EFAULT; f.len = out_len; f.cursor = out_cursor; if (copy_to_user(uargs, &f, sizeof(f))) ret = -EFAULT; } else if (ret == -ERANGE) { /* Nothing was written; `len` now says how much one record needs. */ f.len = out_len; if (copy_to_user(uargs, &f, sizeof(f))) ret = -EFAULT; } kvfree(buf); return ret; } /* * One syscall, four argument blocks. They share a prefix — `size`, then `op` — so the size the * caller declares is what says which one arrived. That is the whole point of putting `size` * first: an interface that cannot grow has to be replaced, and this one grows by being given a * new block with a new size. */ SYSCALL_DEFINE2(cube, unsigned int, op, void __user *, uargs) { __u32 size; if (copy_from_user(&size, uargs, sizeof(size))) return -EFAULT; if (size == sizeof(struct cube_args)) return cube_args_op(op, uargs); if (size == sizeof(struct cube_enum_args)) return cube_enum_op(op, uargs); if (size == sizeof(struct cube_range_args)) return cube_range_op(uargs); if (size == sizeof(struct cube_flag_scan_args)) return cube_flag_scan_op(uargs); return -EINVAL; }