Live data from Hacker News

size_t-to-int vulnerability in Linux’s filesystem layer

openwall.com

61–70 of 280 posts

Re: size_t-to-int vulnerability in Linux’s filesystem layer

#61
post #54
post #47

Earlier quoted context omitted.

Haha, yeah JS integers "can grow". Kind of. And then they bite you in the worst way possible. Especially when combined with cryptography (e.g. nonces). Try this: >> const x = 288230376151711740; >> x == x + 1 true Or this: >> 2\*1024 Infinity JS doesn't even have integers. It only has floats. JS is by far the worst language I know of when it comes to integer support. If you want a better example of arbitrary precisio…

I was working at Twitter when we learned this the hard way. We switched to a separate service for generating tweet IDs (instead of MySQL sequences), which for various distributed systems reasons meant IDs started taking up more bits. And when we stuck those bigger IDs into JSON responses in the API... well, we learned the dumbest lesson possible about the intersection between JSON, JavaScript, and double-precision fl…

Yep, I can totally relate. I found multiple bugs in a MessagePack JS implementation that were related to this issue.

I also worked on a project once where unique IDs of objects could get quite large (because they were random u64 integers). Those IDs were serialized and sent to a browser. Sometimes two objects were viewed as "the same" in the browser application because their IDs were truncated by the floating point precision issue.

Re: size_t-to-int vulnerability in Linux’s filesystem layer

#62
post #32

Earlier quoted context omitted.

> Some Rust developers argue that even "size as i32" should be avoided, and "size.try_into()" should be used instead, since it forces the programmer to treat an overflow explicitly at runtime, instead of silently wrapping. It's important to still have the option for efficient truncating semantics, though; some software (e.g. emulators) needs to chunk large integers into 2/4/8 smaller ones, and rotation + truncating a…

It would be nice if CPU's had an instruction for "read the low 8 bits of this register, and require the high bits all be zeros (otherwise throw an exception)". Then safety is free...

What does "otherwise throw an exception" mean at the CPU level?

I know Erlang's BEAM VM has a "fail jump pointer register", where instructions that can fail have relative-jump offsets encoded as immediates for those instructions, and if the instruction "fails" in whatever semantic sense, it takes the jump.

But most CPUs don't have anything like that.

Would you want it to trap, like with integer division by zero?

CPU traps are pretty hard to handle in most language runtimes, such that most compilers generate runtime checks to work around them, rather than attempting to handle them.

Re: size_t-to-int vulnerability in Linux’s filesystem layer

#63
post #32

Earlier quoted context omitted.

> Some Rust developers argue that even "size as i32" should be avoided, and "size.try_into()" should be used instead, since it forces the programmer to treat an overflow explicitly at runtime, instead of silently wrapping. It's important to still have the option for efficient truncating semantics, though; some software (e.g. emulators) needs to chunk large integers into 2/4/8 smaller ones, and rotation + truncating a…

Right, this is a small infelicity in Rust, it is easier to write let x = size as i32; ... even if what you meant was closer to let x: i32 = size.try_into().expect("We are 100% sure size is small enough to fit into x"); But at least it isn't C or C++ where you might accidentally write x = size; ... and the compiler doesn't even warn you that size is bigger than x and you need to think about what you intended. It's rea…

> and the compiler doesn't even warn you that size is bigger than x

That's not true tho, compiler with reasonable flags set will definitely warn you and if you really don't like this kind of code you can force compiler to issue an error instead

Re: size_t-to-int vulnerability in Linux’s filesystem layer

#64
post #32

Earlier quoted context omitted.

> Some Rust developers argue that even "size as i32" should be avoided, and "size.try_into()" should be used instead, since it forces the programmer to treat an overflow explicitly at runtime, instead of silently wrapping. It's important to still have the option for efficient truncating semantics, though; some software (e.g. emulators) needs to chunk large integers into 2/4/8 smaller ones, and rotation + truncating a…

Right, this is a small infelicity in Rust, it is easier to write let x = size as i32; ... even if what you meant was closer to let x: i32 = size.try_into().expect("We are 100% sure size is small enough to fit into x"); But at least it isn't C or C++ where you might accidentally write x = size; ... and the compiler doesn't even warn you that size is bigger than x and you need to think about what you intended. It's rea…

Narrowing within { } initialization is forbidden C++ now

Re: size_t-to-int vulnerability in Linux’s filesystem layer

#65
post #32
post #7

This kind of issue is the reason why some more modern languages like Rust or Go do not have implicit narrowing conversions. For instance, on Rust, trying to simply pass an usize (Rust's equivalent of size_t) to a function which expects an i32 (Rust's equivalent of int) will not compile; the programmer has to write "size as i32" (Rust's equivalent of "(int) size"), which makes it explicit that it might truncate the va…

> Some Rust developers argue that even "size as i32" should be avoided, and "size.try_into()" should be used instead, since it forces the programmer to treat an overflow explicitly at runtime, instead of silently wrapping. It's important to still have the option for efficient truncating semantics, though; some software (e.g. emulators) needs to chunk large integers into 2/4/8 smaller ones, and rotation + truncating a…

As a interesting side note:

The "as" operator is often considered to have been a mistake. Both because of unchecked casts and because of "doing to much".

So I wouldn't be surprised if in the (very) long term there will be a rust edition deprecating `as` casts (after we have alternatives to all cast done with `as`, which are: Pointer casts, dyn casts/explicit coercion and truncating integer casts, for some we already have alternatives for on stable for other not).

And for all who want to not have `as` today you can combine extension traits (which internally still use `as`) + clippy lint against any usage of `as`.

EDIT: I forgot widening integer casts in the list above ;-).

Re: size_t-to-int vulnerability in Linux’s filesystem layer

#66
post #7

This kind of issue is the reason why some more modern languages like Rust or Go do not have implicit narrowing conversions. For instance, on Rust, trying to simply pass an usize (Rust's equivalent of size_t) to a function which expects an i32 (Rust's equivalent of int) will not compile; the programmer has to write "size as i32" (Rust's equivalent of "(int) size"), which makes it explicit that it might truncate the va…

Long and int are the same size on Windows. The fact that these sizes aren't well defined is the cause of the issue.

Re: size_t-to-int vulnerability in Linux’s filesystem layer

#67
post #60
post #8

I love the little nugget in the mitigations section. You can plug the hole for a normal filesystem, but then FUSE filesystems have an additional problem: "if an attacker FUSE-mounts a long directory (longer than 8MB), then systemd exhausts its stack, crashes, and therefore crashes the entire operating system (a kernel panic)." If there's one place other than the kernel where truly defensive programming should be appl…

Wait how does a user space daemon exhausting its stack lead to a kernel panic?

PID 1 is special in Unix systems. As the parent of all other processes (and child to none) it's not clear to the kernel what should happen when it exits.

Re: size_t-to-int vulnerability in Linux’s filesystem layer

#68
post #3
post #2

> Unfortunately, this size_t is also passed to functions whose size argument is an int (a signed 32-bit integer), not a size_t. Is this the type of things that could be caught by a linter or strict compilation rules? This seems to be to be a failure of the type system.

AFAIK most compilers by default will output a warning in this case.

They can't warn by default, because on some platforms long->int isnt a narrowing conversion.

Re: size_t-to-int vulnerability in Linux’s filesystem layer

#69
post #60
post #8

I love the little nugget in the mitigations section. You can plug the hole for a normal filesystem, but then FUSE filesystems have an additional problem: "if an attacker FUSE-mounts a long directory (longer than 8MB), then systemd exhausts its stack, crashes, and therefore crashes the entire operating system (a kernel panic)." If there's one place other than the kernel where truly defensive programming should be appl…

Wait how does a user space daemon exhausting its stack lead to a kernel panic?

Because the kernel intentionally panics [1] if the init process would otherwise exit in any way – whether because it called exit(), it was killed, or, in this case, it crashed.

This is likely because Unix semantics treat the init process specially: any process whose parent dies is re-parented to the init process. It's not clear what should happen to these processes if the init process itself went away, so the kernel just gives up.

[1] https://elixir.bootlin.com/linux/latest/source/kernel/exit.c...

Re: size_t-to-int vulnerability in Linux’s filesystem layer

#70
post #32

Earlier quoted context omitted.

> Some Rust developers argue that even "size as i32" should be avoided, and "size.try_into()" should be used instead, since it forces the programmer to treat an overflow explicitly at runtime, instead of silently wrapping. It's important to still have the option for efficient truncating semantics, though; some software (e.g. emulators) needs to chunk large integers into 2/4/8 smaller ones, and rotation + truncating a…

Right, this is a small infelicity in Rust, it is easier to write let x = size as i32; ... even if what you meant was closer to let x: i32 = size.try_into().expect("We are 100% sure size is small enough to fit into x"); But at least it isn't C or C++ where you might accidentally write x = size; ... and the compiler doesn't even warn you that size is bigger than x and you need to think about what you intended. It's rea…

If you initialize it like this you get a warning:

x = { size };

Post reply on HN