#include "types.h" #include "defs.h" #include "param.h" #include "spinlock.h" #include "sleeplock.h" #include "fs.h" #include "file.h" #include "x86.h" #include "errno.h" extern int consoleread(struct inode*, uint64, char*, int); extern int consolewrite(struct inode*, uint64, char*, int); extern int mouseread(struct inode*, uint64, char*, int); // --- /dev/random and /dev/urandom ------------------------------------------- // // This was a linear congruential generator seeded with a constant, which means // **every boot produced the same bytes in the same order**. Not weak: the same. // A program asking for a key, a nonce, a temporary filename or a hash seed got // one it shared with every other SchOS machine that had ever run. // // What is here now is still not a CSPRNG and does not pretend to be. It is an // LCG that is *seeded* from something real and re-stirred from the cycle // counter on every read, which fixes the one property that made the old one // dangerous rather than merely poor. See "What it does not do" in // docs/architecture/devices.md. static uint64 rseed; static int rseed_ready; static void rseed_init(void); // RDRAND, if this CPU has it. CPUID leaf 1, ECX bit 30. // // Checked rather than assumed: the instruction faults with #UD on a CPU that // does not implement it, and "the random device is a kernel panic" is a worse // failure than a weak seed. static int rdrand64(uint64 *out) { uint32 a, b, c, d; unsigned char ok = 0; uint64 v = 0; cpuid_leaf(1, &a, &b, &c, &d); if (!(c & (1u << 30))) return 0; // Ten tries, per Intel's own guidance: the instruction is allowed to fail // when its entropy pool is momentarily empty, and it says so in CF. for (int i = 0; i < 10; i++) { asm volatile("rdrand %0; setc %1" : "=r"(v), "=qm"(ok) :: "cc"); if (ok) { *out = v; return 1; } } return 0; } // Seeded at boot rather than on the first read. // // Lazy would work, but it puts the one line that says where the entropy came // from in the middle of whatever the machine was doing hours later, and it // makes the first reader pay for the CPUID. Called from main(); the lazy path // stays as a belt-and-braces for anything that reads before init runs. void random_init(void) { if (!rseed_ready) rseed_init(); } static void rseed_init(void) { uint64 v; rseed_ready = 1; if (rdrand64(&v)) { rseed = v; kinfo("random: seeded from RDRAND\n"); return; } // No RDRAND. The cycle counter is the only thing here that is different on // every boot and not written down anywhere -- it is not entropy in any // serious sense, but it is not a constant either, which was the whole // problem. Mixed with the wall clock so that two machines starting at the // same instant do not agree. rseed = rdtsc(); rseed ^= boot_epoch * 0x9E3779B97F4A7C15ULL; rseed ^= (uint64)(uintptr_t)&rseed; if (rseed == 0) rseed = 0x9E3779B97F4A7C15ULL; kinfo("random: no RDRAND; seeded from the cycle counter and the clock\n"); } int nullwrite(struct inode *ip, uint64 off, char *dst, int n) { return n; } int nullread(struct inode *ip, uint64 off, char *dst, int n) { return 0; } int zeroread(struct inode *ip, uint64 off, char *dst, int n) { memset(dst, 0, n); return n; } int randomread(struct inode *ip, uint64 off, char *dst, int n) { if(!rseed_ready) rseed_init(); for(int i = 0; i < n; i++){ // Re-stirred from the cycle counter every byte, so two reads a moment // apart do not continue one sequence -- and so that a process cannot // predict the next caller's bytes from its own. rseed ^= rdtsc(); rseed = rseed * 6364136223846793005ULL + 1442695040888963407ULL; dst[i] = (rseed >> 33) & 0xFF; } return 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, uint64 off, char *dst, int n) { return -ENOSPC; } int mousewrite(struct inode *ip, uint64 off, char *dst, int n) { return n; } int memread(struct inode *ip, uint64 off, char *dst, int n) { switch(ip->minor) { case 1: return consoleread(ip, off, dst, n); case 2: return nullread(ip, off, dst, n); case 3: return consoleread(ip, off, dst, n); /* 10 = /dev/tty, 11..16 = /dev/tty1..tty6. consoleread resolves the minor to a terminal; see vt_for_minor(). */ case 10: case 11: case 12: case 13: case 14: case 15: case 16: return consoleread(ip, off, dst, n); case 5: return zeroread(ip, off, dst, n); case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros case 8: case 9: return randomread(ip, off, dst, n); // Not "operation not permitted", which is what -1 means once it reaches // userspace. There is no device on this minor. default: return -ENODEV; } } int memwrite(struct inode *ip, uint64 off, char *dst, int n) { switch(ip->minor) { case 1: return consolewrite(ip, off, dst, n); case 10: case 11: case 12: case 13: case 14: case 15: case 16: return consolewrite(ip, off, dst, n); case 7: return fullwrite(ip, off, dst, n); // /dev/full: always ENOSPC case 3: // /dev/keyboard: discarded case 2: case 5: case 8: case 9: return nullwrite(ip, off, dst, n); default: return -ENODEV; } } struct devsw devsw[] = { [0] = { 0, 0 }, [1] = { memread, memwrite }, // /dev/fb0 (major 2) — the framebuffer, kernel/dev/fb.c. This used to be // { nullread, nullwrite }, so writing pixels to it succeeded and did // nothing. // The close hook gives the text console its screen back when whatever was // drawing on the framebuffer exits — see fbclose(). [2] = { fbread, fbwrite, 0, fbclose }, [3] = { mouseread, mousewrite }, [4] = { 0, 0 }, // /dev/tty (major 5) — alias for the console [5] = { memread, memwrite }, // /proc status files (major 6) — see kernel/dev/procfs.c [6] = { procread, procwrite }, // /dev/input/eventN (major 7) — raw evdev events, kernel/dev/input.c. The // minor is the device index, so event0 is the keyboard and event1 the mouse. [7] = { inputread, inputwrite, inputready, inputclose, inputopen }, // /dev/hdN (major 8) — raw disks, kernel/dev/disk.c. The minor is the IDE // device number. No `ready` hook: a disk is always ready, which is what // fileread assumes when one is absent. [8] = { diskread, diskwrite }, // /dev/pci (major 9) -- PCI devices reachable from userspace, for drivers // that do not live in the kernel. Registers arrive through mmap and // everything else through ioctl; see include/pciuser.h. [9] = { pciuser_read, pciuser_write }, // /dev/dbg (major 10) -- ask the kernel a question. Write a line, read the // answer; see include/dbg.h and docs/architecture/debugging.md. [10] = { dbg_read, dbg_write }, // /dev/netdev (major 11) -- a process, being a network interface. The // reverse of major 9: that hands a device to a process, this lets a process // be one. See include/netuser.h. [11] = { netuser_read, netuser_write, netuser_ready }, // /dev/dsp (major 12) -- UNIX audio, kernel/dev/audio.c. Write PCM to it. // The close hook plays out what is still queued: a program that writes a // sound and exits expects to hear the end of it. [12] = { dspread, dspwrite, 0, dspclose }, };