jcalvinowens
Location: Bay Area, CA, USA
Remote: Yes
Willing to relocate: No
Technologies: C, C++, Linux, drivers, embedded, HPC, networking, video, radio, yocto
Résumé/CV: https://github.com/jcalvinowens/misc/blob/main/resume/resume.pdf
Email: [email protected]
I solve technical problems in exchange for monetary compensation. I do a little bit of everything: https://github.com/jcalvinowensI'm not considering full time roles at this time, only contract work. Thanks.
Location: Bay Area, CA, USA
Remote: Yes
Willing to relocate: No
Technologies: C, C++, Linux, drivers, embedded, HPC, networking, video, radio, yocto
Résumé/CV: https://github.com/jcalvinowens/misc/blob/main/resume/resume.pdf
Email: [email protected]
I solve technical problems in exchange for monetary compensation. I do a little bit of everything: https://github.com/jcalvinowensI currently have 20 hours/week available. I'm not considering full time roles at this time, only contract work. Thanks.
GCC has historically been a bit of a pig about -Os, my favorite example is on x86 where it will emit a runtime divide for a division by a compile time constant power of two because it saves a byte of text!
int fn(int n)
{
return n / 8;
}
..becomes: 0000000000000000 <fn>:
0: 89 f8 mov %edi,%eax
2: b9 08 00 00 00 mov $0x8,%ecx
7: 99 cltd
8: f7 f9 idiv %ecx
a: c3 ret
..versus: 0000000000000000 <fn>:
0: 8d 47 07 lea 0x7(%rdi),%eax
3: 85 ff test %edi,%edi
5: 0f 49 c7 cmovns %edi,%eax
8: c1 f8 03 sar $0x3,%eax
b: c3 retDDR5 on-die ECC detects and corrects one-bit errors. It cannot detect two-bit errors, so it will miscorrect some of them into three-bit errors. My understanding is that the on-die error correction scheme is specifically specially designed such that the resulting three-bit errors are mathematically guaranteed to be detected as uncorrectable two-bit errors by a standard full system-level ECC running on top of the on-die ECC. But I've never found a real authoritative reference that directly says that.
If a NAS has a 1G or even 2.5G NIC, improving the filesystem performance is a waste of time... the network is the bottleneck. My N100 two-disk btrfs-raid1 NAS hits line rate on a 2.5G NIC, that's all that matters to me.
{0}[calvinow@sousa ~/git/linux] git describe
v7.1
{0}[calvinow@sousa ~/git/linux] git clean -dffxq
{0}[calvinow@sousa ~/git/linux] zcat /proc/config.gz > .config
{0}[calvinow@sousa ~/git/linux] time make -skj32 tar-pkg
'./System.map' -> 'tar-install/boot/System.map-7.1.0'
'.config' -> 'tar-install/boot/config-7.1.0'
'./vmlinux' -> 'tar-install/boot/vmlinux-7.1.0'
'arch/x86/boot/bzImage' -> 'tar-install/boot/vmlinuz-7.1.0'
real 0m56.539s
user 18m41.863s
sys 2m8.754sIf you don't actually need all the drivers, you can use "make localmodconfig" to substantially reduce that. My local kernels build in 90 seconds on a 32-thread desktop machine :)
The kernel is a lot more stable than people think: I run the daily linux-next on my Debian stable gaming PC to look for bugs, and I don't find very many.
Based on comments in the kernel source, it seems like the userspace fsck for JFS and F2FS will also sometimes create /lost+found. There might be more that do.
Yes, it's copy on write... but there is a linear relationship between the size of the process and the number of page table entries required to represent it.
A 2x32GB DDR5 kit I paid $150 for 11 months ago costs $910 today from the same retailer. A 2x16GB DRR4 kit that was $105 last year is now $230.
The RAM alone in my newest machine would sell for over double what I paid for the entire machine one year ago.
In the cloud, you don't pay the power bill and you have no reason to care. But it's not always like that.
Here's a little reproducer: https://gist.github.com/jcalvinowens/da57edda9a01ca9f4c4088a...
$ gcc -O2 test.c -o test
$ strace -e socket,connect,write,accept,read,close ./test --rx
<...>
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
accept(3, NULL, NULL) = 4
close(3) = 0
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096
<...>
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 3035
read(4, "", 4096) = 0
close(4) = 0
+++ exited with 0 +++
$ strace -e socket,connect,write,accept,read,close ./test --tx
<...>
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(31337), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 600000) = 600000
close(3) = 0
+++ exited with 0 +++
...versus: $ gcc -O2 -DWRITE_TO_SOCKET_BEFORE_READ test.c -o test
$ strace -e socket,connect,write,accept,read,close ./test --rx
<...>
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
accept(3, NULL, NULL) = 4
close(3) = 0
write(4, "\250\3\0\0\0\0\0\0\250\3\0\0\0\0\0\0$\0\0\0\0\0\0\0$\0\0\0\0\0\0\0"..., 4096) = 4096
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096
<...>
read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 997
read(4, 0x7ffd45c2d3c0, 4096) = -1 ECONNRESET (Connection reset by peer)
<...>
+++ exited with 1 +++
$ strace -e socket,connect,write,accept,read,close ./test --tx
<...>
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(31337), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 600000) = 600000
close(3)
+++ exited with 0 +++In a world where they are forked, having a randomized heap base in each worker would also defeat the brute force approach. Instead of just fork(), it could execve() itself with some arguments that tell it to be a worker and where to find its brain, that effectively do an ASLR for each worker.
Related, I have a little end-to-end example of a piece of hardware with an STM32 running bare metal firmware like this: https://github.com/jcalvinowens/ledboard
Of course it is... in a typical threaded daemon, the threads have randomized stack addresses. Exactly as you observed, you get unlimited tries because nginx dutifully restarts the worker process with the same literal stack address every time it segfaults. I'm willing to bet the ASLR break they claim to have relies on that, but I'd be happy to be proven wrong if they publish it :)
<source>:5:10: warning: multiple unsequenced modifications to 'a' [-Wunsequenced]
5 | a = a++ + ++a;
|
<source>:5:7: warning: operation on 'a' may be undefined [-Wsequence-point]
5 | a = a++ + ++a;
| ~~^~~~~~~~~~~I know they claimed they can bypass it: if that's true, they should publish it. The forking nature of nginx is uniquely bizarre and vulnerable, and I strongly suspect that's the only way they're pulling it off. I feel like that's the interesting thing here, not the buffer overrun.