errno: -1 is EPERM, and four places were saying it by accident
Ctrl-C killed the shell. consoleread() breaks off the read in progress when
SIGINT arrives and returned -1 to do it -- and -1 is -EPERM by the time it
reaches a program, so every program that handles SIGINT by carrying on saw a
permanent error instead of an interrupted call:
zsh: error on TTY read: operation not permitted
schinitd: restarting os.schos.console-shell
and the next Ctrl-C did it to the replacement. EINTR now, which is what a
signal-interrupted read is required to give. Verified: Ctrl-C an interruptible
command, shell survives, zero restarts.
Same mistake, same file family: link, unlink, rename and renameat returned -1
on every error path. Callers branch on errno -- `if (unlink(p) < 0 && errno !=
ENOENT)` is the standard shape, and against EPERM it always complains -- which
is why zsh reported failures for things that had worked. They return ENOENT,
EEXIST, EXDEV, EISDIR, ENOTEMPTY and EFAULT now. About ninety other `return -1`s
remain in sys_fs.c; these four are done because these four were what a program
tripped over. /dev/mem's unknown minors return ENODEV rather than -1 too.
/dev/urandom was a linear congruential generator seeded with a **constant**, so
every boot produced the same bytes in the same order -- not weak, the same. A
key, a nonce, a temporary filename: shared with every SchOS machine that had
ever run. Seeded at boot from RDRAND where the CPU has it, from the cycle
counter and the wall clock where it does not, and re-stirred per byte. Still an
LCG and still not a CSPRNG; what changed is the part that was dangerous rather
than merely poor.
**Not fixed, and recorded as such:** rename() still will not replace an existing
target, so zsh's history save fails on every exit after the first. An attempt to
unlink the target first was reverted -- the directory is an HTree, dirunlink
followed by dirlink of the same name did not behave, and it panicked in
filewrite with the old file already gone. Losing both files is worse than
refusing, so it refuses until the HTree's slot reuse is understood.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
eacb13ce35
commit
75ccf9841c
11 changed files with 330 additions and 42 deletions
|
|
@ -12,7 +12,84 @@ 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;
|
||||
// --- /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)
|
||||
|
|
@ -36,9 +113,16 @@ zeroread(struct inode *ip, uint64 off, char *dst, int 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 >> 32) & 0xFF;
|
||||
dst[i] = (rseed >> 33) & 0xFF;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
|
@ -72,7 +156,9 @@ memread(struct inode *ip, uint64 off, char *dst, int n)
|
|||
case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros
|
||||
case 8:
|
||||
case 9: return randomread(ip, off, dst, n);
|
||||
default: return -1;
|
||||
// Not "operation not permitted", which is what -1 means once it reaches
|
||||
// userspace. There is no device on this minor.
|
||||
default: return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +174,7 @@ memwrite(struct inode *ip, uint64 off, char *dst, int n)
|
|||
case 5:
|
||||
case 8:
|
||||
case 9: return nullwrite(ip, off, dst, n);
|
||||
default: return -1;
|
||||
default: return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue