
Toward race-free process signaling - pandalicious
https://lwn.net/Articles/773459/
======
theamk
> it is done using a new system call named procfd_signal(). This system call
> operates on a file descriptor of a process; the previous discussions
> convinced Brauner that this is a solution preferred over an ioctl().

This is quite a pity! I do a lot of Python and Bash programming, and regular
file write will be simple and easily supported.

ioctl would require some magic constants and arch dependence, but would still
be relatively safe - there are no dangerous ioctl operators you can do on a
open /proc directory entry, and ioctl has sane error reporting.

the "new syscall" path is the worst of them all -- I am literally two
characters away from invoking all sorts of dangerous commands like "kill all
processes", "shutdown machine" and so on.

~~~
cyphar
The problem with an ioctl is that ioctls are not namespaced and so a buggy
userspace program might end up being tricked into killing a process (if the
ioctl overlaps).

ioctl also doesn't have exceptionally sane error reporting, especially when
we're comparing it to syscalls.

In addition, this is the first step toward more generic procfds and for some
other use cases ioctls are simply insufficient and it would be strange to have
a mismash of both APIs.

> I am literally two characters away from invoking all sorts of dangerous
> commands like "kill all processes", "shutdown machine" and so on.

Can you explain what you mean? Syscalls are the least bad solution because we
are guaranteed no reasonably buggy program will have used the syscall already.

Writing to a procfile would be significantly worse than either, but ioctls
still have problems.

~~~
theamk
> The problem with an ioctl is that ioctls are not namespaced and so a buggy
> userspace program might end up being tricked into killing a process (if the
> ioctl overlaps).

I am not sure what is the threat model here -- it If we have a buggy userspace
program which can be tricked into opening arbitrary files by an un-authorized
user, it is already game over. What you do after is irrelevant -- the writes
would damage the system, reads would leak info. The fact that they won't be
able to kill processes does not help much if they could overwrite /sbin/init
instead. As Tycho Andersen said in [0], "It seems completely theoretical to
me."

> ioctl also doesn't have exceptionally sane error reporting, especially when
> we're comparing it to syscalls.

I could be wrong here, but I thought that most ioctl's do not generally cause
the program to crash, they just fail with meaningful error code. On the other
hand, syscalls will cause the process to segfault.

>> I am literally two characters away from invoking all sorts of dangerous
commands like "kill all processes", "shutdown machine" and so on.

> Can you explain what you mean?

Sure, let's say this goes in, and I want to use it ASAP. Libc takes ages to
update, so I have no wrappers. I am using Python. What are my options?

Option 1: File

    
    
         f = open('/proc/%d/kill' % pid, 'w')
         ...
         f.write("9")
         f.close()
    

What kind of mistakes I can make? I can mistype '/proc/%d/kill' format string,
I can write() the wrong thing.

What is the worst that could happen? I will get nice exception (like "File not
found" during open or "Invalid operation") during close. In the the worst
case, I write to "/proc/%d/mem" and damage the process instead, but this not
very likely -- this typo is easy to spot. And it will only affect the process
I want killed anyway.

Summary: Please give me that option!

Option 2: ioctl

    
    
         PSKILLCTL = (0x80046A01 if sys.bits == 32 else 0x80046E01)
         f = open('/proc/%d' % pid, 'w')
         ...
         fcntl.ioctl(f, PSKILLCTL, 9)
         f.close()
    

What kind of mistakes I can make? I can get the constant value wrong, I can
get ioctl args wrong (maybe it needs pointer to integer)

What is the worst that could happen? ioctl will succeed but will do the wrong
thing, i.e. not kill the process. Luckily, there are very few ioctls which
/proc/%d supports, and none of them will have permanent effect. Yeah, maybe
this will switch to non-blocking mode or we will get aio enabled on the handle
-- who cares, we will close it right away.

Summary: meh, finding ioctl codes are annoying with all the macros but I can
live with this.

Option 3: syscall

    
    
         __NR_KILL_VIA_PID = (382 if sys.bits == 32 else 731)
         libc = ctypes.CDLL(None)
         f = open('/proc/%d' % pid, 'w')
         ...
         libc.syscall(__NR_KILL_VIA_PID, f.fileno(), 9)
         f.close()
    

What kind of mistakes I can make? I can get the constant value wrong, I can
get syscall args wrong (maybe it needs a struct)

What is the worst that could happen? I reboot machine (sys_shutdown). I kill
all processes on this tty (sys_vhangup). I mess up my entire process in a
subtle way (sys_vfork, sys_modify_ldt). I break many other processes on the
machine (sys_sethostname).

Summary: I really want to make sure I get this right. I feel this is like
shaving with straight edge -- don't make mistakes.

Hopefully, I have explained my point. Note that this is from POV of python
programmer -- I know that from the C land, it all looks different. But if we
have Python-only packages, or a shell scripts, adding a new C program is a
significant burden.

Could you elaborate why do you think writing to procfile will be worse?
Especially if we already have writable /proc/%d/mem.

[0] [https://lwn.net/ml/linux-
kernel/20181119223954.GA4992@cisco/](https://lwn.net/ml/linux-
kernel/20181119223954.GA4992@cisco/)

