How exactly is this a bug in Win32? We can argue about whether or not this was a good design choice, but it wasn’t Win32 that opened an HTTP connection, wrote the response stream to disk and then executed the file without any kind of validation.
Linden probably deserves the bulk of the blame, but it is a little weird that, if Windows sees a file with an .exe extension that but no PE structure, it will just happily take whatever bits it gets and run them directly. But I guess if COM executables genuinely had no structure than there was nothing else that could be done. Yet again, back-compatibility is Microsoft's savior and curse at once.
As much as I want to blame Windows for it, this seems like reasonable behavior if the updater software is passing bytes straight from the HTTP stream. Unfortunate that the 404 page happened to have a byte sequence that would be interpreted as a COM, but the Updater should have been validating the downloaded exe in the first place. Not just for 404 but for actual .exe files that may be malicious
> Unfortunate that the 404 page happened to have a byte sequence that would be interpreted as a COM
That's the thing. Any byte sequence which didn't have an EXE header was interpreted as a COM executable. And in the (NT)VDM environment, there aren't a lot of illegal operations, so executing random data (like an HTML error page) can cause quite a bit of unpredictable behavior before it crashes or gets caught in a loop.
COM files indeed had no structure. DOS would load the file contents to the bottom 64k of memory starting at 0x100, set code, data and stack segments to zero, the stack pointer to zero (so the first push would wrap around to 64k), and jump to 0x100. And that was as much complexity at the linkage had.
It's almost hard to remember how easy it was to deal with the hardware back then. We view things as simpler now, but just getting a toolchain installed via rustup or npm or whatever is multiple blog posts of complexity, vs. the paragraph above plus a half-sheet of paper required to scribble down the instruction set and BIOS hooks.
COM files have no structure; they're loaded directly into a segment and control transferred to the first byte. I think the smallest valid COM file is a single return instruction.
Certainly the bulk of the blame falls on Second Life and the printer manufacturers for not validating inputs.
But there's another valuable lesson about interpreting binary/string blobs: don't make guesses about the format of the data from its value. You'll eventually get data that looks like one thing but isn't, and do the wrong thing with it.
I run into this all the time. Just a couple weeks ago I spent a lot of time trying to track down a bug where a programmer had decided to use a de-serialization function that would take the value of a JSON field, look for an internal object with that ID and either return the object it found or initialize a new object using the string as a value (and a random ID). So guess what happened when I tried to create a new object with a value that happened to be the same as the ID of an existing object?
The Win32 issue is just looking at the first few bytes of the input to determine its format (and how to try executing it) - it's somewhat hard to avoid here for legacy reasons, but a better design would use metadata associated with the input to determine its format rather than guessing.
>but a better design would use metadata associated with the input to determine its format rather than guessing.
how would that have prevented this incident? if the metadata is stored in the filesystem (as opposed to the file contents), chances are, the updater is going to blindly mark whatever it downloaded as "an executable". if it's part of the file contents, it's essentially the same as magic bytes.
>A Win32 executable file begins with a so-called "MZ" header, followed by a so-called "PE" header. If the "PE" header cannot be found, then the loader attempts to load the program as a Win16 executable file, which consists of an "MZ" header followed by an "NE" header.
>If neither a "PE" nor an "NE" header can be found after the "MZ" header, then the loader attempts to load the program as an MS-DOS relocatable executable. If not even an "MZ" header can be found, then the loader attempt to load the program as an MS-DOS non-relocatable executable (aka "COM format" since this is the format of CP/M .COM files).
It is easy to "physically break" a thermal printer. Simply stop the servo motor while continuing to provide the power to the printer element heater. Your printer will soon catch fire. I've done it a few times myself while writing printer drivers.
> In the event of a printing stall, and occasionally during normal operation, the fusing oven would heat paper to combustion. This fire risk was aggravated by the fact that if the printer continued to operate, it would essentially stoke the oven with fresh paper at high speed.
There’s a fun story out there about software physically breaking a hard drive. At the time, this was actually quite difficult for drivers to avoid doing. So, when a certain manufacturer came out with a drive it finally considered safe against driver errors, it proclaimed a bounty for anyone who could prove them wrong.
The winner put the drive on a table, connected with a cable, but not attached to a case. Then he wrote a routine that slammed the read head to one side, then slowly moved it back. Each time the routine looped, the drive would scoot across the table until it finally fell off the edge.
Back in 1993 I was thumbing through a book about assembly programming for MS-DOS, and it seemed that you could theoretically cause all kinds of hardware problems. IIRC the method that stood out to me at the time was intentionally accelerating disk drive motors. Were there built-in limits? On some drives or all? The book didn't say.
I'm still not sure I read that right, but man was it fascinating to think about all the control one could wield over the head of one's high school electronics teacher and his room full of new PC's.
That reminds me of the IBM "Black Team" QA group story where a guy writes a perfect tape drive driver, formally verified and everything. The "Black Team" was annoyed they couldn't find a problem with it, so they wrote a program to spin the tape at the cabinet's resonant frequency, until the cabinet eventually fell over.
When I was 13 I wrote a C program that wrote random data to a file. I had the idea to try executing these files. Most of them did nothing, but I remember that one did end up causing the printer to print out pages of gibberish.
Years later I found myself wondering how that even worked in the first place. Surely the chance of generating a valid executable was basically zero. Now I know why.
I wish I had kept that executable, it would've been interesting to disassemble it.
Nothing quite as fun as piping a postscript to a printer in you college Unix lab and watching garbage being spewed out. Meanwhile due to misconfigured permissions, you cannot cancel the job. So you quietly shut of the printer and walk out of the lab.
I tried to teach myself postscript by writing directly to the new laserprinters at school .. and was met the next day by a very angry grad student sysadmin waving two reams of mostly postscript error pages. Something about not being in his accounting budget since I was an undergrad annoyed him. Fortunately, he decided that the only solution was to give me access to his departments new Sun workstations running NeWS.
You should have installed the PostScript error handler that prints up giant posters of your error messages and stack dumps scaled and tiled across many pages in a gargantuan font. Maybe they would have given you a really big printer instead of a Sun running NeWS.
We had 8086 IBM machines in our typing class. I remember writing a program to send random bits to the big dot matrix printer. I'd keep a buffer and when the printer did something strange I'd save the buffer and replay it till I isolated the sequence. When a substitute would show up the printer would put on a show ;)
So disappointing. I was expecting a story about a bug in both 2nd life and Windows that somehow caused an action in the virtual world to affect he host one a la 13th floor. Maybe next time. [Please fix the title.]
44 comments
[ 3.4 ms ] story [ 81.8 ms ] threadThat's the thing. Any byte sequence which didn't have an EXE header was interpreted as a COM executable. And in the (NT)VDM environment, there aren't a lot of illegal operations, so executing random data (like an HTML error page) can cause quite a bit of unpredictable behavior before it crashes or gets caught in a loop.
It's almost hard to remember how easy it was to deal with the hardware back then. We view things as simpler now, but just getting a toolchain installed via rustup or npm or whatever is multiple blog posts of complexity, vs. the paragraph above plus a half-sheet of paper required to scribble down the instruction set and BIOS hooks.
Or you can have one file interpreted multiple ways: http://www.nyx.net/~gthompso/poly/polyglot.txt
But there's another valuable lesson about interpreting binary/string blobs: don't make guesses about the format of the data from its value. You'll eventually get data that looks like one thing but isn't, and do the wrong thing with it.
I run into this all the time. Just a couple weeks ago I spent a lot of time trying to track down a bug where a programmer had decided to use a de-serialization function that would take the value of a JSON field, look for an internal object with that ID and either return the object it found or initialize a new object using the string as a value (and a random ID). So guess what happened when I tried to create a new object with a value that happened to be the same as the ID of an existing object?
The Win32 issue is just looking at the first few bytes of the input to determine its format (and how to try executing it) - it's somewhat hard to avoid here for legacy reasons, but a better design would use metadata associated with the input to determine its format rather than guessing.
how would that have prevented this incident? if the metadata is stored in the filesystem (as opposed to the file contents), chances are, the updater is going to blindly mark whatever it downloaded as "an executable". if it's part of the file contents, it's essentially the same as magic bytes.
You could say the same about the file extension, meta data or any other means.
The best thing you can do is write your code to validate the input and to fail early, explicitly, and safely if it runs into a problem.
>A Win32 executable file begins with a so-called "MZ" header, followed by a so-called "PE" header. If the "PE" header cannot be found, then the loader attempts to load the program as a Win16 executable file, which consists of an "MZ" header followed by an "NE" header.
>If neither a "PE" nor an "NE" header can be found after the "MZ" header, then the loader attempts to load the program as an MS-DOS relocatable executable. If not even an "MZ" header can be found, then the loader attempt to load the program as an MS-DOS non-relocatable executable (aka "COM format" since this is the format of CP/M .COM files).
XF86Config used to come with similar dire warnings about not overdriving monitors.
https://en.m.wikipedia.org/wiki/Lp0_on_fire
Ouch. Talk about failing open.
The winner put the drive on a table, connected with a cable, but not attached to a case. Then he wrote a routine that slammed the read head to one side, then slowly moved it back. Each time the routine looped, the drive would scoot across the table until it finally fell off the edge.
I'm still not sure I read that right, but man was it fascinating to think about all the control one could wield over the head of one's high school electronics teacher and his room full of new PC's.
http://www.penzba.co.uk/GreybeardStories/TheBlackTeam.html
Years later I found myself wondering how that even worked in the first place. Surely the chance of generating a valid executable was basically zero. Now I know why.
I wish I had kept that executable, it would've been interesting to disassemble it.
-lets eval() this random piece of bytes we got from non secure connection
-lets curl | sudo sh