HN user

thasso

488 karma

https://thasso.xyz

Posts4
Comments20
View on HN

I believe that inference systems not using the Python stack (which I do not appreciate) are a way to free open models usage and make AI more accessible.

What's wrong with the Python stack? I have never much used it or any other ML stack so I'm genuinely curious.

Your example on read(2) is a good one. There's no way to fix it purely by changing the API because, by nature, the user chooses the size of the buffer.

The difference is that fd_set is a structure that's not defined by the user. If fd_set had a standard size, the kernel could verify that nfds is within the allowed range for the fd_set structure. The select(2) system call would be harder to misuse then, although misuse would still be possible by passing custom buffers instead of pointers to fd_set structures. In that sense, I think we agree on the "problem".

It's indeed just a bit of Unix history, but I was surprised by it nonetheless.

I don't get where my misunderstanding lies. Didn't I point out that the __copy_to_user call returns EFAULT if the memory is unmapped or unwritable? The problem is that some parts of the user stack may be mapped and writable although they're past the end of the fd_set structure.

there's no formal API for it in the glibc headers

The author claims you can pass nfds > 1024 to select(2).If you use the fd_set structure with a size of 1024, this may lead to memory corruption if an FD > 1023 becomes ready if I understand correctly.

This part is bewildering to me:

Now, if you try to watch file descriptor 2000, select will loop over fds from 0 to 1999 and will read garbage. The bigger issue is when it tries to set results for a file descriptor past 1024 and tries to set that bit field in say readfds, writefds or errorfds field. At this point it will write something random on the stack eventually crashing the process and making it very hard to debug what happened since your stack is randomized.

I'm not too literate on the Linux kernel code, but I checked, and it looks like the author is right [1].

It would have been so easy to introduce a size check on the array to make sure this can't happen. The man page reads like FD_SETSIZE differs between platforms. It states that FD_SETSIZE is 1024 in glibc, but no upper limit is imposed by the Linux kernel. My guess is that the Linux kernel doesn't want to assume a value of FD_SETSIZE so they leave it unbounded.

It's hard to imagine how anyone came up with this thinking it's a good design. Maybe 1024 FDs was so much at the time when this was designed that nobody considered what would happen if this limit is reached? Or they were working on system where 1024 was the maximum number of FDs that a process can open?

[1]: The core_sys_select function checks the nfds argument passed to select(2) and modifies the fd_set structures that were passed to the system call. The function ensures that n <= max_fds (as the author of the post stated), but it doesn't compare n to the size of the fd_set structures. The set_fd_set function, which modifies the user-side fd_set structures, calls right into __copy_to_user without additional bounds checks. This means page faults will be caught and return -EFAULT, but out-of-bounds accesses that corrupt the user stack are possible.

A nice thing about C is that you can be pretty confident that you know all major footguns (assuming you spent some time reading about it). With languages that are young or complex there is a much greater chance you’re making a terrible mistake because you’re not aware of it.

I'm shocked every time I go to City Hall and wait while the clerk types my name letter by letter with two fingers. Doesn't he do that every day?! How as it never occurred to him or anyone else that maybe, just maybe, they would benefit from a typing course. It’s just one example of a pattern I’ve noticed with a lot of office workers.

If you are interested in debuggers, there was a post series by Sy Brand a few years back:

https://blog.tartanllama.xyz/writing-a-linux-debugger-setup/

Eli Bendersky also wrote about debuggers (I think his post is a great place to start):

https://eli.thegreenplace.net/2011/01/23/how-debuggers-work-...

I was fascinated with debuggers a while back exactly because they were so mysterious to me. I then wrote a ptrace debugger myself [1]. It features pretty simple implementations of the most common stuff you would expect in a debugger. Though I made the grave mistake of formatting all the code in GNU style.

[1] https://github.com/thass0/spray/tree/main/src

The world’s preeminent linguist Noam Chomsky, and one of the most esteemed public intellectuals of all time, whose intellectual stature has been compared to that of Galileo, Newton, and Descartes, tackles these nagging questions in the interview that follows.

By whom?

Why don't we hear about this happening to people who are equally wealthy in classical (non-crypto) assets? Are they more discreet and harder to make out or are there protections in place at, e.g., banks that limit the efficacy of these kinds of attacks? I guess most wealth people don't have enough of their wealth in liquid assets to be a good target but people with lot's of crypto assets can easily transfer it all.

On File Formats 1 year ago

For archive formats, or anything that has a table of contents or an index, consider putting the index at the end of the file so that you can append to it without moving a lot of data around. This also allows for easy concatenation.

I’m really fascinated by hypervisors but I struggled to find resources when first trying to get into them. Would love it if someone could point me to more content like this.

Systems languages have these weird semantics to allow writing performant low-level code and abstracting over the hardware at the same time (unsafe Rust too, check out its memory model for example). This doesn’t make any sense. Either you want a high level language to write correct code on many machines, or, if you want to make code fast, you want a language that allows writing for a specific platform. The C standard is stupid, just turn on -fno-strict-aliasing like any sane person would and get on with it.

The worst part about uninitialized variables is that they frequently are zero and things seem to work until you change something else that previously happened to use the same memory.

This is not the whole story. You're making it sound like uninitialized variables _have_ a value but you can't be sure which one. This is not the case. Uninitialized variables don't have a value at all! [1] has a good example that shows how the intuition of "has a value but we don't know which" is wrong:

  use std::mem;
  
  fn always_returns_true(x: u8) -> bool {
      x < 120 || x == 120 || x > 120
  }
  
  fn main() {
      let x: u8 = unsafe { mem::MaybeUninit::uninit().assume_init() };
      assert!(always_returns_true(x));
  }
If you assume an uninitialized variable has a value (but you don't know which) this program should run to completion without issue. But this is not the case. From the compiler's point of view, x doesn't have a value at all and so it may choose to unconditionally return false. This is weird but it's the way things are.

It's a Rust example but the same can happen in C/C++. In [2], the compiler turned a sanitization routine in Chromium into a no-op because they had accidentally introduced UB.

[1] https://www.ralfj.de/blog/2019/07/14/uninit.html

[2] https://issuetracker.google.com/issues/42402087?pli=1

I agree that zero-initializing doesn't really help avoid incorrect values (which is what the author focuses on) but at least you don't have UB. This is the main selling point IMO.

You can do lot's of the same things in C too, as the author mentions, without too much pain. See for example [1] and [2] on arena allocators (which can be used exactly as the temporary allocator mentioned in the post) and on accepting that the C standard library is fundamentally broken.

From what I can tell, the only significant difference between C and Odin mentioned in the post is that Odin zero-initializes everything whereas C doesn't. This is a fundamental limitation of C but you can alleviate the pain a bit by writing better primitives for yourself. I.e., you write your own allocators and other fundamental APIs and make them zero-initialize everything.

So one of the big issues with C is really just that the standard library is terrible (or, rather, terribly dated) and that there is no drop-in replacement (like in Odin or Rust where the standard library seems well-designed). I think if someone came along and wrote a new C library that incorporates these design trends for low-level languages, a lot of people would be pretty happy.

[1] https://www.rfleury.com/p/untangling-lifetimes-the-arena-all...

[2] https://nullprogram.com/blog/2023/10/08/