Live data from Hacker News

Beep security update

debian.org

51–60 of 103 posts

Re: Beep security update

#51
post #19

The author of beep needs to read the POSIX specification on async-signal-safety [1]. In particular, it is not safe to call exit, free, ioctl, putchar, or perror from a signal handler. [1] http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2...

> In particular, it is not safe to call exit, free, ioctl, putchar, or perror from a signal handler. Why is it accepted by the compiler, then?

I'll be quicker to blame the signal API than the programming language on that front. Dealing with unix signals correctly and robustly is far from trivial and rife with footguns. For instance I believe that Rust still doesn't have a good general purpose solution for handling signals that doesn't involve the libc and unsafe code.

Signal is basically the crappiest form of IPC available on a modern operating system short of emulating a mouse and keyboard and typing into the other application's terminal window.

Re: Beep security update

#52
post #46

Earlier quoted context omitted.

It's not something that can be solved on an API level. You can rewrite the C standard library all you want, you still won't be able to differentiate between this: void handle_signal(struct signal_handler_arg *arg) which does signal handling and this: void check_signal_info(struct signal_handler_arg *arg) which validates a struct signal_handler, is called outside a signal handler, and doesn't do any signal handling. S…

> you still won't be able to differentiate between this > which validates a struct signal_handler, is called outside a signal handler, and doesn't do any signal handling. Let's use a pseudo OO syntax: void foo(arg: argtype) { arg.do_thing(); // ok frob(arg); // not possible - if frob might be forbidden anywhere then it's not callable like this } That frob is forbidden isn't because the compiler detected that foo is a…

I really don't know what you mean by this:

> That frob is forbidden isn't because the compiler detected that foo is a signal handler, but because no forbidden functions exist.

or this:

> not possible - if frob might be forbidden anywhere then it's not callable like this

Re: Beep security update

#53

Also see https://holeybeep.ninja/

The patch given on that page includes the line:

    !id>~/pwn.lol;beep # 13-21 12:53:21.000000000 +0100
Not bothering to test it, but I don't think that contributes to patching beep. Why do people always need to be annoying?

Re: Beep security update

#54
post #43
post #3

https://github.com/johnath/beep/issues/11#issuecomment-37838...

The Debian patch with diff highlighting: https://gist.github.com/jwilk/561ae35894756aae1e31503d0c52db... AFAICS, it does this: 1) Fixes use of uninitialized memory. 2) Fixes double-free in the signal handler. 3) Makes sure that the device is opened only once. I still don't understand what exactly the bug is supposed to be. I also don't understand what is the purpose of 3). With this fix applied, it's still possible f…

How did you find the Debian patch? http://git.deb.at/w/pkg/beep.git doesn't seem to have any changes.

Re: Beep security update

#55

Earlier quoted context omitted.

It can't be done in general. A signal handler is just a function that at runtime is connected to a signal. With some work many or most unsafe handlers in the wild can be detected, but so can many other problems, I guess.

I'll bite. A helpful compiler would at an appropriate warning level tell you when you are setting a signal handler with a function you haven't (helpfully) annotated with some construct to say "this thing is a signal handler". Said helpful compiler would then also use this helpful annotation to forbid or loudly warn against a helpful subset of naught functions it is able to deduce are being called therein because huma…

Seems like a good job for static analysis/linting vs compiler.

Re: Beep security update

#56
post #52

Earlier quoted context omitted.

> you still won't be able to differentiate between this > which validates a struct signal_handler, is called outside a signal handler, and doesn't do any signal handling. Let's use a pseudo OO syntax: void foo(arg: argtype) { arg.do_thing(); // ok frob(arg); // not possible - if frob might be forbidden anywhere then it's not callable like this } That frob is forbidden isn't because the compiler detected that foo is a…

I really don't know what you mean by this: > That frob is forbidden isn't because the compiler detected that foo is a signal handler, but because no forbidden functions exist. or this: > not possible - if frob might be forbidden anywhere then it's not callable like this

I was trying to describe how an api would look where the compiler would make sure you could not call a certain function from a certain context. So I was trying to argue that then the api would have to be such that you can't actually import that function to that context.

So basically: say that putchar is the forbidden function. Then there exists no global function "putchar". There is no way to just "import" a header and call putchar in our language/api.

Instead, everything available to a function is what's passed to it (an interface to available API). So for example for a particular type of context (a handler, say) - a very restricted api is passed in as argument, meaning the code inside that function has very little api surface area to play with.

Re: Beep security update

#57
post #39
post #19

Earlier quoted context omitted.

> In particular, it is not safe to call exit, free, ioctl, putchar, or perror from a signal handler. Why is it accepted by the compiler, then?

This is a really good point actually, aren't there static analysis tools that can figure out things like this? I know in JS we have linters to stop you from doing things that are most likely not very smart. I find this very useful, a lot more useful than to say "If you don't even read the manpages there is no help in sight". Maybe the compiler should not do it but perhaps you could run a linter against packages that…

C has separate compilation: the compiler isn’t guaranteed to have all of the relevant source code at one time.

When the signal handler is compiled, there’s nothing to say “this will be used as a signal handler,” so while the compiler has the source it doesn’t have a reason to complain about calling any particular functions.

Then, when that function is used as a signal handler, the compiler has the source to the calling code (well, the code setting up the callback) but may not have the source for the handler itself. So now it knows which rules apply but may not have the code it needs to enforce those rules. If they’re in the same file, it could. And while that is a common case, it’s not the only possibility.

How much should the compiler or linter know about the platform’s API? How can I tell the compiler about any arbitrary rule my own code must follow?

As someone else suggested, you could do it with annotations, but that’s nonstandard.

Re: Beep security update

#58
post #52

Earlier quoted context omitted.

I really don't know what you mean by this: > That frob is forbidden isn't because the compiler detected that foo is a signal handler, but because no forbidden functions exist. or this: > not possible - if frob might be forbidden anywhere then it's not callable like this

I was trying to describe how an api would look where the compiler would make sure you could not call a certain function from a certain context. So I was trying to argue that then the api would have to be such that you can't actually import that function to that context. So basically: say that putchar is the forbidden function. Then there exists no global function "putchar". There is no way to just "import" a header a…

> I was trying to describe how an api would look where the compiler would make sure you could not call a certain function from a certain context.

It's not a certain set of functions that you want to forbid, it's a certain behaviour (e.g. being interruptible by signals, or being non-reentrancy). Forbidding functions won't get you anywhere. You can always forbid write(), but you won't be able to forbid functions that dynamically dispatch to write() at runtime, or user-defined functions that aren't reentrant.

Edit: i.e. when we say you shouldn't call write(2) from a signal handler, that's not because there's a list somewhere in a POSIX standard with functions that you shouldn't call in a signal handler and write(2) is on it \. That's because the POSIX spec allows write(2) to be interrupted by a signal and that's not okay inside a signal handler.

___

\ There might be one, I haven't looked at a POSIX spec in a long time now -- but either way, the point is that such a list is open :-).

Re: Beep security update

#59
post #51
post #19

Earlier quoted context omitted.

> In particular, it is not safe to call exit, free, ioctl, putchar, or perror from a signal handler. Why is it accepted by the compiler, then?

I'll be quicker to blame the signal API than the programming language on that front. Dealing with unix signals correctly and robustly is far from trivial and rife with footguns. For instance I believe that Rust still doesn't have a good general purpose solution for handling signals that doesn't involve the libc and unsafe code. Signal is basically the crappiest form of IPC available on a modern operating system short…

one of my favorite implementation of signal handling has always been DJB's qmail implementation (actually all his code incl djbdns, daemontools etc). His coding style has been a huge inspiration on writing easy to read and secure C.

Re: Beep security update

#60
post #58

Earlier quoted context omitted.

I was trying to describe how an api would look where the compiler would make sure you could not call a certain function from a certain context. So I was trying to argue that then the api would have to be such that you can't actually import that function to that context. So basically: say that putchar is the forbidden function. Then there exists no global function "putchar". There is no way to just "import" a header a…

> I was trying to describe how an api would look where the compiler would make sure you could not call a certain function from a certain context. It's not a certain set of functions that you want to forbid, it's a certain behaviour (e.g. being interruptible by signals, or being non-reentrancy). Forbidding functions won't get you anywhere. You can always forbid write(), but you won't be able to forbid functions that d…

I see. Preventing access to classes of behaviour is a harder problem. Reentrancy (single threaded - such as mutual recursion) must be really hard to detect with certainty other than at runtime, for example. In a multi threaded context it's obviously even harder.

Classifying functions into more categories like "interruptible", "uninterruptible" seems doable with enough language support. It doesn't look to far from what e.g. Rust and C# does with "unsafe", i.e. a status of functions that bubbles up so that your function may be tainted as unsafe by calling unsafe functions. If the number of classifications of functions is small as is the case with unsafe/safe, or uninterruptible/interruptible then it would seem possible to sort this out via compiler support and keywords for example. So for example a signal handler is then an uninterruptible function. It's a compiler error to call even indirectly, or dynamically dispatch to interruptible functions.

For any type of safety, dynamic dispatch obviously needs to be done in a type safe manner, so no pointer arithmetic/function pointer invocation can be allowed. Doing so would make the called code be assumed to be the worst of all categories: unsafe, interruptible etc). This is how unsafe/safe works too.

So while it's cumbersome I'm sure it would be possible to achieve at least to some extent, given an expressive enough language and a clever enough compiler.

Post reply on HN