x86-64 Machine Code (Linux), 40 bytes
0: 67 6f
3: 73 65
6: 64 75 63
Disassembly of section .text:
0: b0 01 mov $0x1,%al
2: 40 b7 01 mov $0x1,%dil
5: be 00 00 00 00 mov $0x0,%esi
a: b2 06 mov $0x6,%dl
c: 0f 05 syscall
e: 0f c7 f1 rdrand %ecx
11: 67 e3 ec jecxz 0 <_start>
14: b0 01 mov $0x1,%al
16: be 00 00 00 00 mov $0x0,%esi
1b: 0f 05 syscall
The first 12 bytes are used to store the strings "goose\n" and "duck\n". Technically when duck is printed, it prints 6 bytes (while "duck\n" is 5 bytes), but chances are that the byte after where duck is located in the binary will be an invisible character.
The blocks that preceded the syscall are just setting up the sys_write system call. Normally in x86-64 this looks like (in Intel syntax rather than AT&T):
b8 01 00 00 00 mov $0x1,%eax ; syscall number for sys_write
bf 01 00 00 00 mov $0x1,%edi ; fd number for STDOUT
48 be 00 00 00 00 00 00 00 00 movabs $0x0,%rsi ; pointer to where the "duck\n" string is located (nasm syntax)
ba 06 00 00 00 mov $0x6,%edx ; number of bytes to write
0f 05 syscall
However we can use the lower bits addressing of the registers to produce shorter x86 opcodes.
For the second syscall, only rax gets clobbered (since it holds the return value for sys_write), so we have to reset it.
The randomness comes from using the
rdrand instruction, which sets a hardware random number into ecx. The next instruction then jumps back to _start if ecx is 0. This means that "duck" will only be printed again with a chance of 1/(2^32). However small this chance is, it technically is possible and non-deterministic.
The more interesting way to print out a series of "duck" strings before "goose" is to replace the rdrand and jecxz instructions with:
66 0f c7 f3 rdrand %bx
66 85 db test %bx,%bx
75 e9 jne 0 <_start>
Unfortunately this takes 2 more bytes, but the effect is that the result of rdrand is stored into bx, which is a 16 bit register. Additionally, each time this is run, the chance the code jumps back to start and prints "duck" again is (2^16 - 1)/(2^16). This means that after about 50000 loops there's over a 50% chance of bx being 0 in at least one of the loops. (The exact amount is 45426 loops). For a 90% chance of bx being 0 in at least one of the loops, just over 150000 loops need to occur.
Finally, with normal program behavior, this program should call sys_exit with this code:
20: b8 3c 00 00 00 mov $0x3c,%eax
25: 31 ff xor %edi,%edi
27: 0f 05 syscall
But since we can just leave this out and the program will exit with a segmentation fault, this is fine. The challenge has already been completed by this point.
For better inline comments, check out the original nasm source code in my repo.