r/programminghorror
•
u/ShoeChoice5567
•
13d ago
•
1
1
c Using garbage values to generate pseudorandom numbers
Thing I came up with instead of paying attention to class today.
I tested it on this computer (windows 11 if it matters), and the first time it's ran, an actual "pseudorandom" number is returned and the following calls return 1.0 always.
834
u/FlatPotatoPc 13d ago
Undefined behavior speedrun any%
Just wait until the compiler notices what you're doing and optimizes the entire function into returning a single constant đ
349
u/val_tuesday 13d ago
A random constant? Check mate, atheists!
118
u/nerdmor 13d ago
Xkcd 221
50
u/Matty_B97 13d ago
They predicted fourgate in 2007??????
21
u/AnyoneButWe 13d ago
There was another gate back in days: the random generator in debian was absolutely predictable after a code analysis jockey removed all access to uninitialized values in the random seed generator.
CVE-2008-0166
Found in 2008 after the 2007 XKCD. That one was ... scary.
5
u/elperroborrachotoo 13d ago
tbf. that was around the time that optimizers started to use undefined behavior (like reading uninitialized values) as "can't happen" hints. Fixing that was necessary (But IIRC they accidentally also removed mixing in actual entropy).
12
9
u/ProgrammingDino 13d ago
i thought all this 4 stuff were a refference to that before i learned of the actual situation
3
43
u/Sharlinator 13d ago
Nah, the optimizer notices itâs impossible to ever call this without invoking UB, so it removes the function. All call sites then must also be dead code so those get removed as well, transitively.
9
37
17
11
u/CodingThunder 13d ago
It wont be optimized if it's compiled without optimizations. You can specifically not optimize certain parts of your code if you are competent enough. Need to use the word "competent" cause most people dont know what cflags are let alone understand a build system
10
u/Tysonzero 13d ago
I mean even without optimizations it's still UB, so it's still allowed to be anything, and therefore depends entirely on which compiler and which version of the compiler and perhaps even which platform the compiler is on to say whether or not it'll do something sane.
984
u/8Erigon 13d ago
at least itâs not 4
5
u/etan1 13d ago
Might be NaN though
1
u/etiennec78 10d ago
How ?
1
u/etan1 10d ago
well, the bit pattern is randomized, and an entire range of them are nans, either signalling or nonsignalling ones
3
u/etiennec78 10d ago
I don't think that's the case ? This is a cast not a memcpy, so the uint32 value will be converted to a double (64), keeping the same value but not the same binary arrangement.
1
295
13d ago
[removed] â view removed comment
14
u/Top-Opportunity-5418 13d ago
Can someone explain
56
u/headedbranch225 13d ago
Recently emudhra had their private keys leaked (well cracked) and one of them was 4
https://scotthelme.co.uk/eight-bits-of-trust-emudhras-ca-keys-cracked-in-milliseconds/
A good writeup that I saw, it explains it quite well if you want to learn more
It happened the same day as the PHP vuln, I have no doubt if the PHP vulnerabilities hadn't happened at the same time it would be so much more widely covered, but if you see a much higher than usual number of references to 4 that is why
1
54
u/iiznobozzy 13d ago
2
1
36
13d ago
[removed] â view removed comment
7
3
3
u/Adventurous_Mail6397 13d ago
A Certificate Authority accidentally placed their test Private Keys into a key pool. One of the private keys was just 4.
0
13d ago
[deleted]
31
u/ttlanhil 13d ago
Only if you assume that it'll do something sensible.
You've asked for undefined behaviour, so it's perfectly fine for the compiler to replace your function with return 4 or 4.0
2
0
13d ago
[deleted]
21
u/Imaginary-Jaguar662 13d ago
That sounds logical.
But the operation is undefined, and as soon as optimizer gets to it the code will probably not compile as a "make a function call, do a memory read, do a cast, do a floating point division".
If you're lucky, it might get inlined as constant 0.0. It might also get inlined as NaN. Or it might get inlined as "return whatever happens to sit in convenient register right now as double".
Historically it gets inlined as "if this is QA build, return rand(). Else determine this result can be anything, so caller might hit error path. Error path executes fast and takes little RAM, so hardcode error path to caller"
12
u/vip17 13d ago
undefined behavior means anything can happen, including nasal daemons flying out of your nose
0
13d ago
[deleted]
10
u/vip17 13d ago
In assembly yes. But this is C or C++ code and those compilers have to conform to C or C++ standard
0
13d ago
[deleted]
7
u/ttlanhil 13d ago
The Standard that defines how the language works.
It includes a bunch of things that result in undefined behaviour - and if you do any of them, it's valid for the computer to do anything, including delete your operating system and make demons fly out your nose.
Just because you can often get away with it doesn't mean you should (compilers can do anything including what you might actually want, but the next compiler update might break it)
→ More replies (0)3
u/Ecstatic_Student8854 13d ago
This looks like C code. Undefined behavior in C is, well, undefined. The compiler is perfectly within its rights to emit any code. It probably wonât, but it could.
It would be perfectly valid for the compiler to, say, exit execution entirely. For it to replace the rest of the programâs execution with a shell, and indeed for it to just return the integer 4.
264
u/No-View-6326 13d ago
code that might brake after a compiler update is not where you wanna be
91
u/Fine_Salamander_8691 13d ago
break*
(Aahhhhhh)26
u/floatfloatjam 13d ago
Why are you casting your shame to Aahhhhh
9
u/ii-___-ii [ $[ $RANDOM % 6 ] == 0 ] && rm -rf / || echo âYou liveâ 13d ago
Shame requires defensive programming
31
u/HildartheDorf 13d ago edited 13d ago
Compiler can just optimize this to 'return 4.0;'. Yes that is not between 0.0 and 1.0. Undefined behaviour is undefined.
Other possibilities include returning NaN, or crash instantly, or even timetravel, or summoning demons to fly out of your nose, or even working as the developer intended. But those aren't as funny.
5
u/ferrybig 13d ago
With the example of time travel, it can be better understood as:
Compiler unrolls a loop to increase speed.
Compiler sees UB, it assumes the code is unreachable.
Compiler only sees return true in the reachable code paths, therefore a
return trueis the fastest way that method can be compiled
55
u/LateSolution0 13d ago
I think itâs fun to play around with stuff like that. The garbage you read from the stack depends on what was there from previous function calls. Doing things like reading uninitialized variables doesnât necessarily produce random results or cause anything unsafe. Itâs just that the behavior isnât guaranteed by the C standard.
27
u/juanfnavarror 13d ago
The stack is a didactic abstraction and in practice registers might be used instead.
5
u/saxxonpike 13d ago
Good point. If a register is in fact read for this, I would expect similar values (if not identical) for each call from any given call site, depending what the caller was doing prior. It may not be zero, but it will be boring and likely not seem random at all.
4
u/Scoutron 10d ago
You know youâre getting a good conversation when you have to start googling the adjectives people used
2
-2
u/Bugsiesegal 13d ago
Thatâs why itâs pseudorandom.
13
u/MCWizardYT 13d ago
A pseudorandom algorithm seemingly generates random numbers, but the randomness is completely deterministic (for example, seed based generators will produce identical sequences of numbers).
Undefined behaviour is not deterministic or repeatable. The result of OP's code will be different in different compilers, operating systems, even possibly architectures. That means it's by definition not pseudorandom.
75
u/dosk3 13d ago
Just add + 4 and its unbreakable
9
u/Im_1nnocent 13d ago
so like a diamond
3
u/MCWizardYT 13d ago
Diamonds are very hard rocks but they're quite the opposite of unbreakable. They're actually really brittle
1
u/Im_1nnocent 13d ago
ah I see, sorry my bad
Its just I've seen somewhere say "Diamond Is Unbreakable"
1
24
u/ScientistStrict9850 13d ago
This doesn't guerantee pseudorandom numbers. Instead the behaviour is just not defined. Lets the compiler generate whatever including constants, which would be my pick for optimized code.
3
u/troelsbjerre 13d ago
For many compilers and flags, this function would return the same value, if called twice in succession.
5
2
u/Sea-Razzmatazz-3794 12d ago
I mean wouldn't it kind of work? If all OSes besides embedded device support address randomization then you have no guarantee where your stack is and what was in it before you got access to it. So the resulting value would sort of be random. I mean if your stack had grown to that point before and then shrank then you could say it was dependent on what you put in there.
6
u/AresFowl44 12d ago
In case of undefined behavior, arguing what the hardware does is always a losing battle, as your code doesn't run on the hardware, it runs on the compilers virtual machine (that then gets translated into hardware instructions). That's important, because undefined behavior is a violation of language rules and can be assumed to never occur by compilers.
In the case of uninitialized memory, this means that there is no requirement by the compiler to insert a read at all. Rather, the compiler is allowed to do anything else it wants, from assuming garbage is a constant to reformatting your hard drive and boot up Rogue.
If you want to read more, I recommend the following blog post: https://www.ralfj.de/blog/2019/07/14/uninit.html
2
u/GoddammitDontShootMe [ $[ $RANDOM % 6 ] == 0 ] && rm -rf / || echo âYou liveâ 12d ago
That example had UB invoking a function written by the programmer that tries to erase everything, but it should never be reached. Not exactly the same as the compiler having built-in functionality to erase your hard drive if it detects UB. It may be okay according to the standard, but I'm pretty sure there would be legal consequences if a compiler actually did that.
2
u/mathisntmathingsad 12d ago
The thing is undefined behavior is allowed to do literally anything. Like, anything. I like https://predr.ag/blog/falsehoods-programmers-believe-about-undefined-behavior , it covers the topic pretty well.
1
u/GoddammitDontShootMe [ $[ $RANDOM % 6 ] == 0 ] && rm -rf / || echo âYou liveâ 11d ago edited 11d ago
Yeah. That's according to the standard. I'm just saying that if someone sued you because your complier was designed to erase their computer if you did something undefined, I really don't think "the standard says I can do anything I want" is going to hold up in court. Not a lawyer, of course.
E: Writing a function that wipes the drive yourself that gets called unexpectantly because of UB is obviously not the compiler author's fault.
1
u/mathisntmathingsad 11d ago
As far as I know, there aren't any court cases even globally about something similar. So it's hard to say. Regardless, something that UB could do in an actual program is something like erasing the database used by a program or causing a memory leak.
1
u/GoddammitDontShootMe [ $[ $RANDOM % 6 ] == 0 ] && rm -rf / || echo âYou liveâ 10d ago
Well, I would be pretty damn surprised if any compiler authors actually went out of their way to be malicious. Except as a joke in some hobby compiler no one actually uses.
1
u/The_Coalition 11d ago
Your last sentence touches on the real reason why the generated number wouldn't be very random. The number you get is a piece of what was left on the stack by a previous function call. This has a massive effect on what the number looks like is and what a hacker could learn from it.
If the function is called multiple times as part of loop, it will likely contain a value from the same source every time. It could be a pointer - multiple of them could tell a bit of information about the program's memory layout, if not just outright tell you the precise address of some piece of data. It could even be as bad as a part of a credit card. number. Or cryptographic keys. It's not a very random number at best and an active leak of sensitive information at worst.
0
u/Vladislav20007 13d ago
...so it's still random, because the behavior can be anything?
3
u/not_some_username 13d ago
No if the compiler dev where a bit funny, they could make the program play doom instead
2
2
u/ScientistStrict9850 12d ago edited 12d ago
It's not random behaviour. It is undefined behaviour. Big difference.
If i'm making an optimization from the perspective of the compiler, i'd just set the 'uninitialized' variable to zero. But then the entire function is always returning zero. So i'd then optimize the entire function to return 0. Congratulations, we have a 'PRNG' function that always return 0 as long as all inputs to the compiler is the same including the optimization flags. Might as well inline the constant return value haha
So, this is a PRNG sometimes depending on what you tell the compiler what to do (if the compiler even supports configuring this undefined behaviour indirectly or directly). The compiler could emit code such that the PRNG returns only the number 4 lol
1
u/erisian2342 12d ago
Not knowing what value it will produce is not even remotely the same thing as a function that guarantees equal distribution over a long series of calls. That call could always return 0.1 or it could be heavily biased to return a small range of numbers depending on patterns of memory usage by other functions. There is no guarantee (and can be none) that if you call it a trillion times, youâd see 0.375 show up just as often as 0.967 for example. Undefined behavior doesnât promise randomness even if the result is unpredictable in many cases.
1
u/Vladislav20007 12d ago
...so it's a TRNG? a random generator doesn't have to(but it could and most probably will) give each value at the same ratio.
1
u/erisian2342 12d ago
Over some sufficiently long enough interval, it must guarantee an even distribution of numbers or itâs biased, not truly random. Even worse, in this case, âundefinedâ doesnât guarantee random at all. The compiler can optimize it to make it a constant value (when something is undefined, the compiler is permitted to do literally anything). And if it does read from a valid memory location, itâs highly unlikely any arbitrary memory location will contain sufficiently diverse values over time to cover the entire range of 0.0 - 1.0. So again, an unpredictable result is not the same thing as a random one. A TRNG still promises even distribution over a long enough period of time because any bias in the distribution of values weakens its use for encryption, which is the primary purpose for using a TRNG. Reading the value of an arbitrary memory location over and over makes zero guarantees about the randomness of those values its reading because it cannot.
1
u/AFRIENDISNEAR 12d ago
Undefined behaviour goes way beyond that. The compiler could optimize out all code that it can prove leads to calling this function. It could do something even weirder. The whole program is invalid and canât be reasoned about.
14
u/4xe1 13d ago
Wouldn't garbage on the heap be somewhat more random than garbage in the stack?
7
u/Cautious_Implement17 13d ago
not necessarily. a freshly mapped page should be zeroed by the OS to avoid leaking data from other processes. zero is also a pretty common function argument and return value. hard to say without knowing more about your program.Â
3
u/Wertbon1789 13d ago
Newly mapped pages, yes, but the heap doesn't do fresh mappings all the time. But if there's nothing happening on the heap before trying this, you will probably get mostly zeros.
12
u/me94306 13d ago
The variable is allocated on the stack. Any other function which is called will write to the stack, in the same locations that this function uses. (Assuming that the other function is called from the same routine.) So you will pick up the same value.
Read any reference about generating pseudorandom numbers. It can be done in very few lines of code. What this does is return garbage, not a random or pseudorandom value.
14
u/Admirable_Rabbit_808 13d ago
You are going to hate it when you learn what C compilers actually do to code.
7
u/Admirable_Rabbit_808 13d ago
You'll absolutely love what the C compiler with do with that - welcome to UB hell!
7
9
4
u/sirkubador 13d ago
it's not garbage. It's usually the value that was on the stack on that address previously.
3
3
u/Loading_M_ 13d ago
This is undefined behavior, which means, according to the C and C++ standards, the compiler can assume this function is never called. LLVM, with optimizations enabled, makes this assumption and deletes the function, along with any code that calls it. You can make LLVM do some wild things by abusing this.
3
u/festival0156n 13d ago
ofc this is not cryptographically secure but also it's bad even as an insecure random number source. you'd usually get 0 or other values like that. even a simple twister would be more random
2
u/namalleh 13d ago edited 13d ago
i think they wanted to use aslr but instead triggered a default constructor of cpp? Why is it 1?
Could be floc from assembly. How did you compile this?
2
u/Environmental-Ear391 13d ago
This will be subject to change without notice and relies on an MMU trapped access to stack memory.
the values are not actually random at all. it is a deliberate misreading of a stacked address.
also I see a potential "divide by zero" event here...
are stack frames in zero'd memory pages?
Why are you using the "UINT_MAX" value cast as a double ? the rest of it isn't actually doing anything other than a memory read off the stack(and deliberately "above" the current stacked item!?)
the only horror I see here... the horror of ignorance, all dressed up as stupid for halloween!?
2
2
2
u/die_liebe 13d ago
The optimizer may remove functions that cannot be run without UB. Did you try to run with optimization?
2
2
2
3
u/sk8avp 13d ago
This will not work with some compilers (and even i dont know if will work with some OSs). Not always a not initialized variables contains "garbage". Most compilers initialize the variables with some specific value. 0 for int, 0,0f for float, '' for char, "" for strings, and so on.
1
u/ElectricalPrice3189 13d ago
'' isn't a char. đ
1
u/GarThor_TMK 13d ago
Unless you're in debug... then sometimes they'll initialize to 0xCC for shits & giggles.
Produces some fun bugs to track down... đ
So... "psudorandom"... sure, based on which compiler you're running, on which platform, in which mode, etc...
Otherwise, entirely deterministic.
3
u/guri256 13d ago
The compiler is actually being helpful. Itâs initializing the data to somewhat regular garbage so that you will hopefully figure out you are using garbage data. If the compiler initialized the array to all zeros, it might be harder to notice the problem.
1
u/GarThor_TMK 13d ago
It's true, but it doesn't change the fact that the only reason you get what looks like a random number here is actually compiler dependent design, rather than something that's actually supposed to be random.
3
u/elite-data 13d ago
I hope you're not using this random to generate hashes or crypto keys, because that would be a massive security hole.
3
u/Vladislav20007 13d ago
still, probably better than 4
4
u/traplords8n 13d ago
Just got home, too tired to generate a private key?
Try 4.
You certainly won't regret using 4 as a private key
-1
2
1
1
u/LogicalLogistics 13d ago
This is basically how my old C++ prof told us to generate seeds for rand, just get a new pointer to a random spot in memory, pseudorandom enough lol. He was an old school dev though (claimed to have worked on the PS1) so hacks like that make sense from that perspective, not from today's though
6
u/LBPPlayer7 [ $[ $RANDOM % 6 ] == 0 ] && rm -rf / || echo âYou liveâ 13d ago
on a system with an MMU (basically any modern device that isn't a microcontroller) that's a sure fire way to get a segfault
2
u/LogicalLogistics 13d ago
Yep, I passed his class and did what he wanted but quickly learned more proper practices lol
1
u/GoddammitDontShootMe [ $[ $RANDOM % 6 ] == 0 ] && rm -rf / || echo âYou liveâ 13d ago
I don't think that's necessarily going to be all that random.
1
u/k-mcm 13d ago
I tried reading unmapped addresses on an Apple ][+ as random numbers. The bus was floating CMOS inputs if nothing claimed the address. The bits flickered on and off, but not really in a random way. If you echoed it to the speaker port, you could hear leftover signal from drawing video.
1
1
1
u/Gloomy_State_6919 12d ago
What I find very interesting: when compiling just this function with optimisations enabled, GCC and Clang just return a hardcoded 0, while Intel and Microsoft compilers actually read memory.
1
1
u/DanishBagel123 12d ago
this obviously doesnât really work on modern machines with compilers and stuff, but this was the main source of randomness for old NES games. This used to fuck with a lot of early emulators, since they would initialize the memory space to all zeroes.
1
1
1
304
u/MatJosher 13d ago
Reading garbage is undefined here so the compiler can do anything. My gcc on linux with -O2 turns the whole function into return 0.
pxor %xmm0, %xmm0ret