Make the seccomp filter more restrictive :3

This commit is contained in:
Dpeta 2023-01-28 23:44:13 +01:00
parent 6c3d5dbb21
commit a4caa2065d
2 changed files with 112 additions and 87 deletions

View file

@ -1,7 +1,9 @@
""" """
Applies a seccomp filter on Linux via libseccomp's Python bindings. Applies a seccomp filter on Linux via libseccomp's Python bindings.
May have some security benefits, but since Python and Qt use many calls Has some security benefits, but since Python and Qt use many calls
and are pretty high-level, things are very prone to breaking. and are pretty high-level, things are prone to breaking.
Certain features like opening links almost always break.
Libseccomp's Python bindings aren't available on the pypi, check your distro's Libseccomp's Python bindings aren't available on the pypi, check your distro's
package manager for python-libseccomp (Arch) or python3-seccomp (Debian). package manager for python-libseccomp (Arch) or python3-seccomp (Debian).
@ -21,7 +23,11 @@ pesterchum_log = logging.getLogger("pchumLogger")
def activate_seccomp(): def activate_seccomp():
"""Sets the process into seccomp filter mode.""" """Sets the process into seccomp filter mode."""
if seccomp is None: if seccomp is None:
pesterchum_log.error("Failed to import seccomp.") pesterchum_log.error(
"Failed to import seccomp, verify you have"
" python-libseccomp (Arch) or python3-seccomp (Debian) installed"
" and aren't running a pyinstaller build."
)
return return
# Violation gives "Operation not permitted". # Violation gives "Operation not permitted".
sec_filter = seccomp.SyscallFilter(defaction=seccomp.ERRNO(1)) sec_filter = seccomp.SyscallFilter(defaction=seccomp.ERRNO(1))
@ -31,89 +37,76 @@ def activate_seccomp():
# Allow only UNIX and INET sockets, see the linux manual and source on socket for reference. # Allow only UNIX and INET sockets, see the linux manual and source on socket for reference.
# Arg(0, seccomp.EQ, 1) means argument 0 must be equal to 1, 1 being the value of AF_UNIX. # Arg(0, seccomp.EQ, 1) means argument 0 must be equal to 1, 1 being the value of AF_UNIX.
sec_filter.add_rule(seccomp.ALLOW, "socket", seccomp.Arg(0, seccomp.EQ, 1)) # AF_UNIX # Allow AF_UNIX
sec_filter.add_rule(seccomp.ALLOW, "socket", seccomp.Arg(0, seccomp.EQ, 2)) # AF_INET sec_filter.add_rule(seccomp.ALLOW, "socket", seccomp.Arg(0, seccomp.EQ, 1))
# Allow AF_INET
sec_filter.add_rule(seccomp.ALLOW, "socket", seccomp.Arg(0, seccomp.EQ, 2))
# We can kill ourselves in case of skill issues but not others. # Python/Qt might close itself via kill call in case of error.
sec_filter.add_rule(seccomp.ALLOW, "kill", seccomp.Arg(0, seccomp.EQ, os.getpid())) sec_filter.add_rule(seccomp.ALLOW, "kill", seccomp.Arg(0, seccomp.EQ, os.getpid()))
sec_filter.add_rule(seccomp.ALLOW, "tgkill", seccomp.Arg(1, seccomp.EQ, threading.get_native_id())) sec_filter.add_rule(
seccomp.ALLOW, "tgkill", seccomp.Arg(1, seccomp.EQ, threading.get_native_id())
)
# Allow openat as along as it's not in R+W mode.
# We can't really lock down open/openat further without breaking everything,
# even though it's one of the most important calls to lock down.
# Could probably allow breaking out of the sandbox in the case of full-on RCE/ACE.
sec_filter.add_rule(seccomp.ALLOW, "openat", seccomp.Arg(2, seccomp.NE, 2))
sec_filter.load() sec_filter.load()
def restrict_open():
# Allow only opening for reading/writing
sec_filter.add_rule(seccomp.ALLOW, "openat", seccomp.Arg(0, seccomp.EQ, os.getpid()))
# Required for Pesterchum to run. # Required for Pesterchum to function normally.
# Pesterchum doesn't call most of these directly, there's a lot of abstraction with Python and Qt.
PCHUM_SYSTEM_CALLS = [ PCHUM_SYSTEM_CALLS = [
"access", "access", # Files
"bind", "brk", # Required
"brk", "clone3", # Required
"clone3", "close", # Sockets (Audio + Network)
"close", "connect", # Sockets (Audio + Network)
"connect", "exit", # Exiting
"eventfd2", "exit_group", # Exiting
"exit", "fallocate", # Qt
"exit_group", "fcntl", # Required (+ Audio)
"faccessat", "fsync", # Fsync log files
"faccessat2", "ftruncate", # Required
"fallocate", "futex", # Required
"fcntl", "getcwd", # Get working directory
"fsync", "getdents64", # Files? Required.
"ftruncate", "getgid", # Audio
"futex", "getpeername", # Connect
"getcwd", "getpid", # Audio
"getdents64", "getrandom", # Malloc
"getegid", "getsockname", # Required for sockets
"geteuid", "getsockopt", # Required for sockets
"getgid", "getuid", # Audio
"getpeername", "ioctl", # Socket/Network
"getpid", "lseek", # Files
"getrandom", "memfd_create", # Required (For Qt?)
"getresgid", "mkdir", # Gotta make folderz sometimez
"getresuid", "mmap", # Audio
"getsockname", "mprotect", # QThread::start
"getsockopt", "munmap", # Required (segfault)
"gettid", "newfstatat", # Required (Audio + Path?)
"getuid", "pipe2", # Audio
"ioctl", "poll", # Required for literally everything
"lseek", "pselect6", # Sockets/Network
"memfd_create", "read", # It's read :3
"mkdir", "readlink", # Files
"mmap", "recv", # Network
"mprotect", "recvfrom", # Network + DNS
"munmap", "recvmsg", # Sockets (incl. Audio + Network)
"newfstatat", "rseq", # Required
"openat", "rt_sigprocmask", # Required (segfault)
"pipe2", "select", # Useful for sockets
"poll", "sendmmsg", # Network
"prctl", "sendmsg", # Sockets
"pselect6", "sendto", # Eternal eepy!! Sockets + required for waking up mainloop.
"pwrite64", "setsockopt", # Audio
"read", "statx", # File info
"recv", "uname", # Required
"recvfrom", "write", # Required
"recvfrom",
"recvmmsg",
"recvmsg",
"restart_syscall",
"rseq",
"rt_sigaction",
"rt_sigprocmask",
"rt_sigreturn",
"sched_getaffinity",
"sched_getattr",
"sched_setattr",
"select",
"sendmmsg",
"sendmsg",
"sendto",
"setsockopt",
"shutdown",
"statx",
"umask",
"uname",
"write",
] ]
""" """
@ -130,6 +123,35 @@ EXTRA_CALLS = [
"open", "open",
"time", "time",
"listen", "listen",
]
# Required on launch
LAUNCH_CALLS = [
"prctl",
"faccessat",
"faccessat2",
"pwrite64",
]
# Required before full initialize
PRE_INITIALIZE_CALLS = [
"bind",
"eventfd2",
"getegid",
"geteuid",
"getresgid",
"getresuid",
"gettid",
"recvmmsg",
"restart_syscall",
"rt_sigaction",
"rt_sigreturn",
"sched_getaffinity",
"sched_getattr",
"sched_setattr",
"shutdown",
"umask",
]
# Required for opening links, but opening links still doesn't work anyway.
LINK_CALLS = [
"wait4", # for links? "wait4", # for links?
"clone", # for links? "clone", # for links?
] ]

View file

@ -17,6 +17,7 @@ if os.path.dirname(sys.argv[0]):
import ostools import ostools
import nickservmsgs import nickservmsgs
import pytwmn import pytwmn
if ostools.isLinux(): if ostools.isLinux():
import libseccomp import libseccomp
@ -113,8 +114,9 @@ if ostools.isLinux():
action="store_true", action="store_true",
help=( help=(
"Restrict the system calls Pesterchum is allowed to make via seccomp." "Restrict the system calls Pesterchum is allowed to make via seccomp."
" May have some security benefits, but since Python and Qt use many calls" " Has some security benefits, but since Python and Qt use many calls"
" and are pretty high-level, things are very prone to breaking." " and are pretty high-level, things are prone to breaking."
" Certain features like opening links always break."
" (Requires Linux and libseccomp's Python bindings, not available in frozen builds.)" " (Requires Linux and libseccomp's Python bindings, not available in frozen builds.)"
), ),
) )
@ -1401,10 +1403,6 @@ class PesterWindow(MovingWindow):
self.honk = options["honk"] self.honk = options["honk"]
else: else:
self.honk = True self.honk = True
# Activate seccomp if enabled
if "seccomp" in options:
if options["seccomp"]:
libseccomp.activate_seccomp()
self.modes = "" self.modes = ""
self.sound_type = None self.sound_type = None
@ -1694,6 +1692,11 @@ class PesterWindow(MovingWindow):
self.sincerecv = 0 # Time since last recv self.sincerecv = 0 # Time since last recv
self.lastCheckPing = None self.lastCheckPing = None
# Activate seccomp on Linux if enabled
if "seccomp" in options:
if options["seccomp"]:
libseccomp.activate_seccomp()
@QtCore.pyqtSlot(QString, QString) @QtCore.pyqtSlot(QString, QString)
def updateMsg(self, ver, url): def updateMsg(self, ver, url):
if not hasattr(self, "updatemenu"): if not hasattr(self, "updatemenu"):