-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathfake-sudo.py
executable file
·123 lines (92 loc) · 2.71 KB
/
fake-sudo.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/env python3
"""
Fake Sudo
https://github.com/0ex/fake-sudo
"""
import os, sys, re
import signal
from seccomp import SyscallFilter, Attr, NOTIFY, ALLOW, Arch, NotificationResponse
from seccomp import resolve_syscall
from threading import Thread
def main():
progname, *args = sys.argv[:]
shell = None
while args:
a = args.pop(0)
# convert --a=b → --a b
m = re.match(r'--(.+)=(.*)', a)
if m:
a = m[1]
args.insert(0, m[2])
if a in ['-h', '--help']:
print('usage: %s [-u <user>] [<cmd> <arg>...]' % progname)
sys.exit(0)
elif a in ['-i', '--login']:
shell = ['/bin/bash', '-l']
elif a in ['-s', '--shell']:
# use SHELL env, passwd entry
shell = ['/bin/bash']
elif a in ['-u', '--user']:
u = args.pop(0)
print('%s: ignoring user %s', progname, u)
elif a[0] == '-':
print('ERROR invalid option %s', a)
sys.exit(1)
elif a == '--':
break
else:
args.insert(0, a)
break
if shell and args:
args = shell + ['-c', ' '.join(args)]
elif shell:
args = shell
elif not args:
print('ERROR missing command')
sys.exit(1)
run(['unshare', '-r', '--'] + args)
SYSCALLS = [
'chown', 'chown32', 'lchown', 'lchown32',
'fchown', 'fchown32', 'fchownat',
]
def handle_syscalls(f):
while alive:
notify = f.receive_notify()
syscall = resolve_syscall(Arch(), notify.syscall)
log(f'faking {syscall.decode()}({", ".join(str(a) for a in notify.syscall_args)}) pid={notify.pid}')
f.respond_notify(NotificationResponse(notify, val=0, error=0, flags=1))
def log(msg):
print(f'sudo: {msg}', file=sys.stderr)
alive = True
def handle_signal(sig, frame):
global alive
log(f'signal {sig}')
if sig == signal.SIGCHLD:
alive = False
def run(cmd):
# setup filter
f = SyscallFilter(ALLOW)
f.set_attr(Attr.CTL_TSYNC, 1)
for c in SYSCALLS:
f.add_rule(NOTIFY, c)
f.load()
# interrupt receive_notify when the child is dead
signal.signal(signal.SIGCHLD, handle_signal)
signal.siginterrupt(signal.SIGCHLD, True)
signal.pthread_sigmask(signal.SIG_UNBLOCK, {signal.SIGCHLD})
# we could use this instead
# get_notify_fd()
pid = os.fork()
if pid == 0:
os.execvp(cmd[0], cmd)
log(f'waiting {pid}')
try:
handle_syscalls(f)
except Exception as e:
if alive:
raise
_, rc = os.waitpid(pid, 0)
log(f'done {rc}')
sys.exit(os.WEXITSTATUS(rc))
if __name__ == '__main__':
main()