[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)