From ad2b321d56c3ca0a1d3c48d71b80c5dfdb02fb97 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Tue, 11 Aug 2026 12:15:38 +0200 Subject: [PATCH] dev: raw disks are 64-bit, so a real drive stops wrapping at 4 GB struct file already carried a uint64 offset and bread already took a uint64 block number. The narrowing was one boundary in the middle: devsw's read and write took a 32-bit uint, so fileread passed a 64-bit f->off into a 32-bit parameter and the top half was discarded. The effect was not a refusal at 4 GB but silent wraparound past it. A read at 5 GB returned the bytes at 1 GB, reporting success. disk.c had a DISK_MAX_BYTES constant documenting the limit, and nothing anywhere referenced it -- the cap was described but never enforced, which is the worst of the three options. The signature is now uint64 across all nine files that carry it, and diskread/diskwrite no longer narrow the block number on the way to bread. Widening this is a prerequisite for NVMe and for any real disk: namespaces start well above 4 GB and the failure mode reads valid data from the wrong place. Verified end to end rather than by inspection. A 6 GB sparse image with "PAST4GB-MARKER-OK" written at byte 5368709120, attached as /dev/hdb to a ramdisk-root kernel so device 1 was free: # dd if=/dev/hdb bs=4096 skip=1310720 count=1 | od -c | head -2 0000000 P A S T 4 G B - M A R K E R - O Under the old code that offset truncates to 1 GB, where the image is zeroes. The whole path is exercised: f->off, devsw, diskread, bread and ide.c's LBA48. Worth recording for next time: `make ramroot` stages the ESP itself, and running `make esp` afterwards rebuilds the kernel without RAMROOT and silently undoes it. The tell is a 855 KB kernel where a 25 MB one is expected. 110 [ok], no failures, on the default build. --- include/defs.h | 16 ++++++++-------- include/file.h | 4 ++-- kernel/core/console.c | 4 ++-- kernel/dev/cdev.c | 22 +++++++++++----------- kernel/dev/disk.c | 23 ++++++++++++----------- kernel/dev/fb.c | 4 ++-- kernel/dev/input.c | 4 ++-- kernel/dev/mouse.c | 2 +- kernel/dev/procfs.c | 4 ++-- 9 files changed, 42 insertions(+), 41 deletions(-) diff --git a/include/defs.h b/include/defs.h index 204e57e..b234a2a 100644 --- a/include/defs.h +++ b/include/defs.h @@ -198,8 +198,8 @@ int schfs_kreaddir_dev(uint dev, uint64 dir, uint uptime(void); // procfs.c -int procread(struct inode*, uint, char*, int); -int procwrite(struct inode*, uint, char*, int); +int procread(struct inode*, uint64, char*, int); +int procwrite(struct inode*, uint64, char*, int); // Shared with the /proc filesystem (kernel/fs/procfs.c): one owns the // namespace, the other the content. int proc_gen_global(int id, char *buf, int cap); @@ -208,8 +208,8 @@ void procfs_mount(void); // input.c -- raw evdev event devices void inputinit(void); -int inputread(struct inode*, uint, char*, int); -int inputwrite(struct inode*, uint, char*, int); +int inputread(struct inode*, uint64, char*, int); +int inputwrite(struct inode*, uint64, char*, int); int inputready(struct inode*); void inputclose(struct inode*); void inputopen(struct inode*); @@ -431,13 +431,13 @@ void fbclose(struct inode*); // Called from sys_mmap: a mapping of /dev/fb0 is a licence to draw with no // further syscalls, so it is the last the kernel hears about it. void fb_mapped(void); -int fbread(struct inode*, uint, char*, int); -int fbwrite(struct inode*, uint, char*, int); +int fbread(struct inode*, uint64, char*, int); +int fbwrite(struct inode*, uint64, char*, int); int fb_ioctl(int request, addr_t arg); // dev/disk.c -- /dev/hdN, raw seekable access to a whole disk. The minor is the // IDE device number. Writes bypass every filesystem above them. -int diskread(struct inode*, uint, char*, int); -int diskwrite(struct inode*, uint, char*, int); +int diskread(struct inode*, uint64, char*, int); +int diskwrite(struct inode*, uint64, char*, int); // drivers/net/e1000 -- raw Ethernet. No protocol stack above it yet, so these // are the whole interface: queue a frame, take a frame, and the MAC. int e1000_transmit(const void *frame, int len); diff --git a/include/file.h b/include/file.h index ac669c6..7cf76da 100644 --- a/include/file.h +++ b/include/file.h @@ -123,8 +123,8 @@ struct inode { // table mapping major device number to // device functions struct devsw { - int (*read)(struct inode*, uint, char*, int); - int (*write)(struct inode*, uint, char*, int); + int (*read)(struct inode*, uint64, char*, int); + int (*write)(struct inode*, uint64, char*, int); // Optional. Returns 0 when a read would block, which is the only thing // fileread needs to know to honour O_NONBLOCK without every device driver // growing a flags argument. A device that omits this is treated as always diff --git a/kernel/core/console.c b/kernel/core/console.c index 6062e24..481eca5 100644 --- a/kernel/core/console.c +++ b/kernel/core/console.c @@ -655,7 +655,7 @@ consoleintr(int (*getc)(void)) } int -consoleread(struct inode *ip, uint off, char *dst, int n) +consoleread(struct inode *ip, uint64 off, char *dst, int n) { uint target; int c; @@ -729,7 +729,7 @@ console_input_ready(void) } int -consolewrite(struct inode *ip, uint off, char *buf, int n) +consolewrite(struct inode *ip, uint64 off, char *buf, int n) { int i; diff --git a/kernel/dev/cdev.c b/kernel/dev/cdev.c index b9c1361..2071a91 100644 --- a/kernel/dev/cdev.c +++ b/kernel/dev/cdev.c @@ -8,33 +8,33 @@ #include "x86.h" #include "errno.h" -extern int consoleread(struct inode*, uint, char*, int); -extern int consolewrite(struct inode*, uint, char*, int); -extern int mouseread(struct inode*, uint, char*, int); +extern int consoleread(struct inode*, uint64, char*, int); +extern int consolewrite(struct inode*, uint64, char*, int); +extern int mouseread(struct inode*, uint64, char*, int); static uint64 rseed = 0x2A99554433221100ULL; int -nullwrite(struct inode *ip, uint off, char *dst, int n) +nullwrite(struct inode *ip, uint64 off, char *dst, int n) { return n; } int -nullread(struct inode *ip, uint off, char *dst, int n) +nullread(struct inode *ip, uint64 off, char *dst, int n) { return 0; } int -zeroread(struct inode *ip, uint off, char *dst, int n) +zeroread(struct inode *ip, uint64 off, char *dst, int n) { memset(dst, 0, n); return n; } int -randomread(struct inode *ip, uint off, char *dst, int n) +randomread(struct inode *ip, uint64 off, char *dst, int n) { for(int i = 0; i < n; i++){ rseed = rseed * 6364136223846793005ULL + 1442695040888963407ULL; @@ -46,19 +46,19 @@ randomread(struct inode *ip, uint off, char *dst, int n) // /dev/full — reads as an endless run of zero bytes, every write fails with // ENOSPC. Programs use it to exercise their out-of-disk-space paths. int -fullwrite(struct inode *ip, uint off, char *dst, int n) +fullwrite(struct inode *ip, uint64 off, char *dst, int n) { return -ENOSPC; } int -mousewrite(struct inode *ip, uint off, char *dst, int n) +mousewrite(struct inode *ip, uint64 off, char *dst, int n) { return n; } int -memread(struct inode *ip, uint off, char *dst, int n) +memread(struct inode *ip, uint64 off, char *dst, int n) { switch(ip->minor) { case 1: return consoleread(ip, off, dst, n); @@ -73,7 +73,7 @@ memread(struct inode *ip, uint off, char *dst, int n) } int -memwrite(struct inode *ip, uint off, char *dst, int n) +memwrite(struct inode *ip, uint64 off, char *dst, int n) { switch(ip->minor) { case 1: return consolewrite(ip, off, dst, n); diff --git a/kernel/dev/disk.c b/kernel/dev/disk.c index 256b6ba..dd6ead7 100644 --- a/kernel/dev/disk.c +++ b/kernel/dev/disk.c @@ -31,14 +31,15 @@ #include "file.h" #include "errno.h" -// A byte offset arrives as a 32-bit `uint` from the devsw signature, so a raw -// device is addressable to 4 GB. Larger disks need the signature widened -- -// worth doing before anyone installs onto a real one, and called out here -// rather than discovered as silent wraparound at the 4 GB mark. -#define DISK_MAX_BYTES 0xFFFFFFFFULL +// The byte offset is 64-bit, all the way from struct file's off through the +// devsw signature to bread's block number. It used to narrow to a 32-bit uint +// at the devsw boundary, which capped a raw device at 4 GB -- and the constant +// that documented the cap was never referenced by anything, so the failure was +// not a refusal at 4 GB but silent wraparound past it. Nothing here may narrow +// it again: an NVMe namespace or any modern disk is far beyond that. int -diskread(struct inode *ip, uint off, char *dst, int n) +diskread(struct inode *ip, uint64 off, char *dst, int n) { int dev = ip->minor; @@ -49,8 +50,8 @@ diskread(struct inode *ip, uint off, char *dst, int n) int done = 0; while (done < n) { - uint64 pos = (uint64)off + (uint64)done; - uint blk = (uint)(pos / BSIZE); + uint64 pos = off + (uint64)done; + uint64 blk = pos / BSIZE; uint inblk = (uint)(pos % BSIZE); int want = BSIZE - (int)inblk; if (want > n - done) @@ -66,7 +67,7 @@ diskread(struct inode *ip, uint off, char *dst, int n) } int -diskwrite(struct inode *ip, uint off, char *src, int n) +diskwrite(struct inode *ip, uint64 off, char *src, int n) { int dev = ip->minor; @@ -77,8 +78,8 @@ diskwrite(struct inode *ip, uint off, char *src, int n) int done = 0; while (done < n) { - uint64 pos = (uint64)off + (uint64)done; - uint blk = (uint)(pos / BSIZE); + uint64 pos = off + (uint64)done; + uint64 blk = pos / BSIZE; uint inblk = (uint)(pos % BSIZE); int want = BSIZE - (int)inblk; if (want > n - done) diff --git a/kernel/dev/fb.c b/kernel/dev/fb.c index ad8c344..9abe8de 100644 --- a/kernel/dev/fb.c +++ b/kernel/dev/fb.c @@ -64,7 +64,7 @@ pixel_format(struct fb_var_screeninfo *v, unsigned int bpp) } int -fbread(struct inode *ip, uint off, char *dst, int n) +fbread(struct inode *ip, uint64 off, char *dst, int n) { volatile uchar *fb = vga_fb_ptr(); uint64 size = vga_fb_size(); @@ -98,7 +98,7 @@ fb_mapped(void) } int -fbwrite(struct inode *ip, uint off, char *src, int n) +fbwrite(struct inode *ip, uint64 off, char *src, int n) { volatile uchar *fb = vga_fb_ptr(); uint64 size = vga_fb_size(); diff --git a/kernel/dev/input.c b/kernel/dev/input.c index bc1b399..8c10a62 100644 --- a/kernel/dev/input.c +++ b/kernel/dev/input.c @@ -233,7 +233,7 @@ input_ioctl(struct inode *ip, int request, addr_t arg) // desynchronise the reader permanently -- every later event would be split // across two reads and reassembled wrong. int -inputread(struct inode *ip, uint off, char *dst, int n) +inputread(struct inode *ip, uint64 off, char *dst, int n) { struct input_dev *d; int got = 0; @@ -266,7 +266,7 @@ inputread(struct inode *ip, uint off, char *dst, int n) // evdev accepts writes to set keyboard LEDs. There are no LEDs to set here, // and a program that writes one should not see an error for it. int -inputwrite(struct inode *ip, uint off, char *src, int n) +inputwrite(struct inode *ip, uint64 off, char *src, int n) { return n; } diff --git a/kernel/dev/mouse.c b/kernel/dev/mouse.c index ac21454..05f90b0 100644 --- a/kernel/dev/mouse.c +++ b/kernel/dev/mouse.c @@ -197,7 +197,7 @@ uint8 get_mouse_buttons(void) { } int -mouseread(struct inode *ip, uint off, char *dst, int n) +mouseread(struct inode *ip, uint64 off, char *dst, int n) { struct mouse_data m; int size = sizeof(struct mouse_data); diff --git a/kernel/dev/procfs.c b/kernel/dev/procfs.c index 72cb391..0879258 100644 --- a/kernel/dev/procfs.c +++ b/kernel/dev/procfs.c @@ -346,7 +346,7 @@ proc_gen_global(int id, char *buf, int cap) } int -procread(struct inode *ip, uint off, char *dst, int n) +procread(struct inode *ip, uint64 off, char *dst, int n) { char buf[PROC_BUFSZ]; int len = proc_gen_global(ip->minor, buf, PROC_BUFSZ); @@ -422,7 +422,7 @@ proc_write_sysrq(int user_src, uint64 src, uint n) // rather than through the /proc filesystem. `src` is already a kernel buffer // here, which is why it passes user_src = 0. int -procwrite(struct inode *ip, uint off, char *src, int n) +procwrite(struct inode *ip, uint64 off, char *src, int n) { (void)off;