[HN Gopher] Fixing a Buffer Overflow in Unix v4 Like It's 1973
___________________________________________________________________
Fixing a Buffer Overflow in Unix v4 Like It's 1973
Author : vzaliva
Score : 157 points
Date : 2026-01-08 18:29 UTC (1 days ago)
(HTM) web link (sigma-star.at)
(TXT) w3m dump (sigma-star.at)
| mgerdts wrote:
| What is up with fin? Is it really just writing an int 0 in the
| memory right after some variable present in libc or similar?
| extern fin; if(getpw(0, pwbuf))
| goto badpw; (&fin)[1] = 0;
| flatline wrote:
| According to the chatbot, the first word of `fin` is the file
| descriptor, the second its state. "Reset stdin's flags to a
| clean state".
| oguz-ismail2 wrote:
| Predecessor of extern FILE *stdin;
| formerly_proven wrote:
| I'm guessing v4 C didn't have structs yet (v6 C does, but
| struct members are actually in the global namespace and are
| basically just sugar for offset and a type cast; member access
| even worked on literals. That's why structs from early unix
| APIs have prefixed member names, like st_mode.
| Boltgolt wrote:
| )
| jacquesm wrote:
| Heh. I had the same impulse but then didn't do it, upon
| refreshing the page your comment was there :)
| topspin wrote:
| > I'm guessing v4 C didn't have structs yet
|
| There may have been a early C without structs (B had none,)
| but according to Ken Thompson, the addition of structs to C
| was an important change, and a reason why his third attempt
| rewrite UNIX from assembly to a portable language finally
| succeeded. Certainly by the time the recently recovered v4
| tape was made, C had structs: ~/unix_v4$
| cat usr/sys/proc.h struct proc { char
| p_stat; char p_flag; char
| p_pri; char p_sig; char
| p_null; char p_time; int
| p_ttyp; int p_pid; int
| p_ppid; int p_addr; int
| p_size; int p_wchan; int
| *p_textp; } proc[NPROC]; /* stat codes
| */ #define SSLEEP 1 #define SWAIT 2
| #define SRUN 3 #define SIDL 4 #define
| SZOMB 5 /* flag codes */ #define SLOAD
| 01 #define SSYS 02 #define SLOCK 04
| #define SSWAP 010
| b-kuiper wrote:
| so, is there already somebody that wrote the exploit for it? are
| there any special things to consider exploiting such architecture
| back in the day or do the same basic principles apply?
| b-kuiper wrote:
| EDIT: removed due to low effort and mark-up issues. thank you
| all for your feedback.
| b-kuiper wrote:
| perhaps the downvoters can tell me why they are downvoting?
| i'm curious to hear whether if this would work on unix v4 or
| whether there are special things to consider. I thought i
| would ask claude for a basic example so people could perhaps
| provide feedback. i guess people consider it low effort
| reply? anyway, thanks for your input.
| csnover wrote:
| Your response is a non-sequitur that does not answer the
| question you yourself posed, and you are responding to
| yourself with a chatbot. Given that it is a non-sequitur,
| presumably it is also the case that no work was done to
| verify whether the output of the LLM was hallucinated or
| not, so it is probably also wrong in some way. LLMs are
| token predictors, not fact databases; the idea that it
| would be reproducing a "historical exploit" is nonsensical.
| Do you believe what it says because it says so in a code
| comment? Please remember what LLMs are actually doing and
| set your expectations accordingly.
|
| More generally, people don't participate in communities to
| have conversations with someone else's chatbot, and
| especially not to have to vicariously read someone else's
| own conversation with their own chatbot.
| AgentME wrote:
| The explanation it gives at the start appears to be on the
| right track but then the post has two separate
| incomplete/flawed attempts at coding it. (The first one
| doesn't actually put the expected crypt() output in the
| payload, and the second one puts null bytes in the password
| section of the payload where they can't go.)
| MaulingMonkey wrote:
| > perhaps the downvoters can tell me why they are
| downvoting?
|
| Not one of the actual downvoters, but:
|
| Lack of proper indenting means your code as posted doesn't
| even compile. e.g. I presume there was a `char* p;` that
| had `*` removed as markdown.
|
| Untested AI slop code is gross. You've got two snippets
| doing more or less the same thing in two different
| styles...
|
| First one hand-copies strings character by character, has
| an incoherent explaination about what `pwbuf` actually is
| (comment says "root::", code actually has "root:k.:\n", but
| neither empty nor "k." are likely to be the hash that
| actually matches a password of 100 spaces plus `pwbuf`
| itself, which is presumably what `crypt(password)` would
| try to hash.)
|
| Second one is a little less gross, but the hardcoded
| `known_hash` is again almost certainly incorrect... and if
| by some miracle it was accurate, the random unicode
| embedded would cause source file encoding to suddenly
| become critical to compiling as intended, plus the `\0`s
| written to `*p` mean su.c would hit the `return;` here
| before even attempting to check the hash, assuming you're
| piping the output of these programs to su:
| while((*q = getchar()) != '\n') if(*q++
| == '\0') return;
|
| A preferrable alternative to random nonsensical system
| specific hardcoded hashes would be to simply call `crypt`
| yourself, although you might need a brute force loop as
| e.g. `crypt(password);` in the original would presumably
| overflow and need to self-referentially include the `pwbuf`
| and thus the hash. That gets messy...
| avadodin wrote:
| crypt is defined in assembly at s3 crypt.s and it would
| appear to use the same family of "cryptographic machine"
| as V6's crypt.c but it is even shorter and I can't tell
| if it has bounds checks or not -- V6 limits output size
| to 512.
|
| _edit:_ if hash output length is variable it may be
| impossible to find a solution and then a side channel
| timing attack is probably the best option.
| avadodin wrote:
| someone liked this but note that someone else had already
| determined it is limited to 64 bytes on a previous HN
| post so the overflow hack does work.
| MajesticHobo2 wrote:
| Yeah, somebody came up with one here:
| https://news.ycombinator.com/item?id=46469897
| ChrisArchitect wrote:
| Related:
|
| _An initial analysis of the discovered Unix V4 tape_
|
| https://news.ycombinator.com/item?id=46367744
|
| _Unix v4 (1973) - Live Terminal_
|
| https://news.ycombinator.com/item?id=46468283
| nineteen999 wrote:
| Already patched this on my x86_64 v4 UNIX port. Hehe.
| retrac wrote:
| > x86_64 v4 UNIX port
|
| What compiler are you using?
| nineteen999 wrote:
| gcc. Im also working on a port of the original compiler, but
| that's a much lower priority for me.
| nullpoint420 wrote:
| That sounds really fun!
| SoftTalker wrote:
| I had to use _ed_ once in a very limited recovery situation. I
| don 't remember the details but even vi was not an option. It's
| not terrible if you just need to change a few lines. Using it on
| a teletype to write code all day would get tedious quickly. Full-
| screen editors had to have been an amazing productivity boost.
| fooker wrote:
| The amount of code was relatively low.
|
| Not the million line codebases we have today. 50-100 lines was
| the usual program or script.
| avadodin wrote:
| iirc they were initially using actual ttys(as in typewriters)
| and the input delay was hell which is the reason so many UNIX
| commands are two letters.
|
| So likely they would work on the printout:
| 1,$n
|
| And then input the corrections into ed(1).
| fooker wrote:
| That was one generation before this. In unix v4 times,
| input latency was in the order of ~100ms, basically limited
| by the serial port.
|
| Pretty advanced terminals were starting to show up too -
| https://en.wikipedia.org/wiki/VT100
| irusensei wrote:
| I had to use it when I installed 9front on a computer that has
| no graphics card just a serial port (APU2C2). I had only a
| serial device at 9600bps and the other text editors (sam, acme)
| didn't worked. I wanted to turn it into a CPU server so I can
| use drawterm to access it remotely and that requires editing a
| few files.
| b00ty4breakfast wrote:
| ed makes _a lot_ more sense if you remember they were printing
| everything to paper rather than using a glass tty when it was
| first developed
| butterisgood wrote:
| I think ed is still a great editor for specific tasks. As a
| plan 9/9front user, when you get yourself into trouble, it's
| sometimes the only editor you've got left (like when graphics
| doesn't initialize, which I've not seen on 9front -- ever?)
|
| It's really not bad, and you can use it for scripting like sed,
| but it's clunkier.
| kazinator wrote:
| Remotely exploiting a buffer overflow in Unix like it's 1973.
|
| # ... sound of crickets ...
|
| Wanna see me do it again?
| nineteen999 wrote:
| Remotely? ... this version of UNIX doesn't have any networking.
| w-m wrote:
| The password and pwbuf arrays are declared one right after the
| other. Will they appear consecutive in memory, i.e. will you
| overwrite pwbuf when writing past password?
|
| If so, could you type the same password that's exactly 100 bytes
| twice and then hit enter to gain root? With only clobbering one
| additional byte, of ttybuf?
|
| Edit: no, silly, password is overwritten with its hash before the
| comparison.
| loeg wrote:
| > will you overwrite pwbuf when writing past password?
|
| Right.
|
| > If so, could you type the same password that's exactly 100
| bytes twice and then hit enter to gain root? With only
| clobbering one additional byte, of ttybuf?
|
| Almost. You need to type crypt(password) in the part that
| overflows to pwbuf.
| asveikau wrote:
| A bit of a code review (some details from the patch removed for
| clarity): + register int i;
| q = password; - while((*q = getchar()) != '\n')
| + i = 0; + while((*q = getchar()) != '\n') {
| + if (++i >= sizeof(password)) +
| goto error;
|
| You don't actually need i here. i is the same as (q - password).
| It would be idiomatic C to simply rewrite the loop condition as:
| while (q < password+sizeof(password) && (*q = getchar()) !=
| '\n'). To preserve your "goto error;" part, maybe you could do
| the overflow check when null terminating outside the loop.
| shakna wrote:
| Isn't sizeof only standardised in C89? Wouldn't shock me if
| this form needs to be an rvalue.
|
| The author did try pointer arithmetic:
|
| > I initially attempted a fix using pointer arithmetic, but the
| 1973 C compiler didn't like it, while it didn't refuse the
| syntax, the code had no effect.
| asveikau wrote:
| This surprised me too. The snippet I was quoting from was
| already using sizeof, though.
|
| I missed the blurb about pointer arithmetic. Would be
| interesting to go into detail about what "had no effect"
| means.
| WalterBright wrote:
| Having a buffer with a fixed size is always a red flag for
| further checking.
| WalterBright wrote:
| Back in the 80s, when I was writing a C compiler, C compilers
| typically had a maximum size for string literals. The behavior
| was to detect overflow, issue an error message, and fail
| compilation.
|
| I took a different tack. The buffer was allocated with malloc.
| When a string was larger, it was realloced to a larger size. This
| worked until memory was exhausted, and then the program quit.
|
| It was actually less code to implement than having a fixed size
| buffer.
|
| Ditto for the other compilation limits, such as length of a line.
| The only limit was running out of memory.
| emilfihlman wrote:
| The source has
|
| ttybuf[2] =& ~010;
|
| Which is another bug.
| messe wrote:
| What's the bug? If you're referring to the =& syntax, then
| that's just how &= used to be written in older versions of C.
| serpent wrote:
| Are you sure any buffer overflows were actually fixed in 1973?
___________________________________________________________________
(page generated 2026-01-09 23:01 UTC)