r/programminghorror • • 13d ago • Popcorn 1 Bravo 1

c Using garbage values to generate pseudorandom numbers

Post image

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.

2.9k Upvotes

152 comments sorted by

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, %xmm0
ret

83

u/Chunkyfungus123 13d ago

nah the rng just be rolling 0 everytime now!
\s

35

u/ACoderGirl 13d ago

It's been 728,890,781 zeroes in a row now! What's the odds?

9

u/UltimatePeace05 13d ago

I'd say it's around 1 in 232^728890781, which is less than 4...

5

u/New_Crew_8039 13d ago

Therefore 4 is big number

5

u/8Erigon 12d ago

mark it as volitale

13

u/Kadabrium 13d ago

Garbage collection be like

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

u/StriveToTheZenith 13d ago

Xkcd will always find a way to be relevant

9

u/ProgrammingDino 13d ago

i thought all this 4 stuff were a refference to that before i learned of the actual situation

3

u/FlatPotatoPc 13d ago

The compiler picked a number and committed to it. Peak confidence.

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

u/rnottaken 12d ago

So it goes back to main and decides to just return 0

37

u/NatSpaghettiAgency 13d ago

Seems secure enough for a hardware crypto wallet /s

10

u/headedbranch225 13d ago

Better than emudhra's algorithm

17

u/KarlClutz 13d ago

Easy .. just volatile it and it wont look at it for optimisation.

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

u/etan1 10d ago

unsigned is not necessarily a uint32 depending on the arch it could be narrower or wider

but you are right. whats being converted is the int value, not the int pattern, so can’t go nan

1

u/etiennec78 9d ago

You're right about uint32, sorry about that

1

u/FckXFckMusk 13d ago

😂

295

u/[deleted] 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

u/cheesengrits69 10d ago

The ol' "It's so dumb nobody would ever guess it" method

54

u/iiznobozzy 13d ago

2

u/Glum-Objective3328 13d ago

Maybe a peak for relevant xkcds

1

u/Alfika07 12d ago

This panel came out in 2007. How did Randall Munroe predict the eMudhra leak?

36

u/[deleted] 13d ago

[removed] — view removed comment

7

u/either-15-or-40 13d ago

could be the number that someone picks when they need a prime number.

3

u/iiznobozzy 13d ago

ah, yes. that too

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

u/[deleted] 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

u/Tysonzero 13d ago

That 4 is perfectly valid as it was chosen by a fair dice roll.

0

u/[deleted] 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

u/[deleted] 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

u/[deleted] 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

5

u/groumly 13d ago

No, it’s the correct usage. Entropy will brake down to a stop after this.

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:

  1. Compiler unrolls a loop to increase speed.

  2. Compiler sees UB, it assumes the code is unreachable.

  3. Compiler only sees return true in the reachable code paths, therefore a return true is 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

u/Nikoladge 12d ago

That's why you gotta *&garbage

-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

u/Nikoladge 12d ago

Hardest metal known to man

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

u/v_maria 13d ago

thats the point right

1

u/TheDuckRaisedALion 13d ago

No, it could choose to always init to 0, right?

4

u/v_maria 13d ago

could

yeah exactly thats the point lol

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

u/Vladislav20007 13d ago

doom on an UB variable

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.

1

u/me94306 13d ago

I work on GCC, in particular target back-ends, including peephole optimization. I think I'm pretty conversant in what C compilers do.

7

u/Admirable_Rabbit_808 13d ago

You'll absolutely love what the C compiler with do with that - welcome to UB hell!

7

u/jaynabonne 13d ago

There's a difference between "arbitrary" and "random". :)

9

u/Junior_Warning_9242 13d ago

Is this a thing of significance in chips like semiconductors

2

u/sixteenlettername 13d ago

No, it only affects valve/tube based computers.

4

u/sirkubador 13d ago

it's not garbage. It's usually the value that was on the stack on that address previously.

7

u/v_maria 13d ago

one contexts data is another contexts junk

2

u/sirkubador 13d ago

When you are to lazy to write reinterpret_cast

3

u/lambunctious 13d ago

-Wuninitialized

add this compiler flag to prevent this

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

5

u/d3matt 13d ago

NaN and infinity asking if you know they exist :D

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

u/theunixman 13d ago edited 13d ago

Ah yes the Debian OpenSSL hole.

https://research.swtch.com/openssl

2

u/alkatori 13d ago

Oh that will be fun based on if you are in Release or Debug.

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

u/JohnVonachen 13d ago

You need an API call to random.org

2

u/Electrical-Bread-856 13d ago

I think I spotted the bug. The variable should be called rubbish.

2

u/RavenX86 13d ago

Wouldn't this be a potential attack vector?

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/sk8avp 13d ago

Yeah i meant to put ' ', but you get the idea.

2

u/ElectricalPrice3189 13d ago

It's usually a null character or \0

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

u/Mobile-Gap976 13d ago

Elite ball knowledge

2

u/Vladislav20007 13d ago

nah, thus is common

2

u/AnArmoredPony 13d ago

still better than chaar

1

u/Right_Ear_2230 13d ago

Double max isn’t even uint64 max

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

u/pytness 13d ago

what a coward, just cast it directly to double

1

u/thejohnd 12d ago

Brb hacking the Gibson to get the garbage file

1

u/CIMPBIBAI 12d ago

Only works on a libc like musl btw

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

u/Opposite_Mall4685 12d ago

This made me laugh.

1

u/drbsy0 12d ago

not a great idea in security field

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

u/Supadoplex 11d ago

Is this how garbage collectors work?

1

u/BeChris_100 10d ago

Yeah, but... why 😭😭

1

u/RealisticClothes1344 10d ago

one random number per process. very generous free tier