Skip to content

Instantly share code, notes, and snippets.

@ConnorNelson
Created January 30, 2024 00:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ConnorNelson/d7b7202c714730c5edc4ea1819c4bc0f to your computer and use it in GitHub Desktop.
Save ConnorNelson/d7b7202c714730c5edc4ea1819c4bc0f to your computer and use it in GitHub Desktop.
Landlock in Python
import ctypes
import enum
import os
SYS_landlock_create_ruleset = 444
SYS_landlock_add_rule = 445
SYS_landlock_restrict_self = 446
PR_SET_NO_NEW_PRIVS = 38
LANDLOCK_CREATE_RULESET_VERSION = 1 << 0
LANDLOCK_RULE_PATH_BENEATH = 1
class LandlockAccessFS(enum.IntFlag):
EXECUTE = 1 << 0
WRITE_FILE = 1 << 1
READ_FILE = 1 << 2
READ_DIR = 1 << 3
REMOVE_DIR = 1 << 4
REMOVE_FILE = 1 << 5
MAKE_CHAR = 1 << 6
MAKE_DIR = 1 << 7
MAKE_REG = 1 << 8
MAKE_SOCK = 1 << 9
MAKE_FIFO = 1 << 10
MAKE_BLOCK = 1 << 11
MAKE_SYM = 1 << 12
REFER = 1 << 13
TRUNCATE = 1 << 14
class LandlockRulesetAttr(ctypes.Structure):
_fields_ = [("handled_access_fs", ctypes.c_uint64)]
class LandlockPathBeneathAttr(ctypes.Structure):
_fields_ = [("allowed_access", ctypes.c_uint64),
("parent_fd", ctypes.c_int32)]
libc = ctypes.CDLL("libc.so.6", use_errno=True)
landlock_abi = libc.syscall(SYS_landlock_create_ruleset, 0, 0, LANDLOCK_CREATE_RULESET_VERSION)
if landlock_abi < 0:
errno = ctypes.get_errno()
raise OSError(errno, os.strerror(errno))
landlock_fs_access_rights = [
(LandlockAccessFS.MAKE_SYM << 1) - 1, # v1
(LandlockAccessFS.REFER << 1) - 1, # v2: add "refer"
(LandlockAccessFS.TRUNCATE << 1) - 1, # v3: add "truncate"
]
LandlockAccessFS.ALL = landlock_fs_access_rights[landlock_abi - 1]
def landlock_create_ruleset(ruleset):
ruleset_fd = libc.syscall(SYS_landlock_create_ruleset, ctypes.byref(ruleset), ctypes.sizeof(ruleset), 0)
if ruleset_fd < 0:
errno = ctypes.get_errno()
raise OSError(errno, os.strerror(errno))
return ruleset_fd
def landlock_add_rule(ruleset_fd, rule_type, rule_attr):
err = libc.syscall(SYS_landlock_add_rule, ruleset_fd, rule_type, ctypes.byref(rule_attr), 0)
if err < 0:
errno = ctypes.get_errno()
raise OSError(errno, os.strerror(errno))
def landlock_restrict_self(ruleset_fd):
err = libc.syscall(SYS_landlock_restrict_self, ruleset_fd, 0)
if err < 0:
errno = ctypes.get_errno()
raise OSError(errno, os.strerror(errno))
def landlock(path_access, *, no_new_privs=True):
ruleset_fd = landlock_create_ruleset(LandlockRulesetAttr(handled_access_fs=LandlockAccessFS.ALL))
for path, access in path_access.items():
path_beneath = LandlockPathBeneathAttr(allowed_access=access,
parent_fd=os.open(path, os.O_PATH | os.O_CLOEXEC))
if path_beneath.parent_fd < 0:
errno = ctypes.get_errno()
raise OSError(errno, os.strerror(errno))
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, path_beneath)
os.close(path_beneath.parent_fd)
if no_new_privs:
if libc.prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0:
errno = ctypes.get_errno()
raise OSError(errno, os.strerror(errno))
landlock_restrict_self(ruleset_fd)
return ruleset_fd
with open("/flag", "w") as f:
f.write("FLAG")
# Allow full access to /bin, /lib, /lib64; and read access to /flag
# Nothing else is allowed
landlock({
"/bin": LandlockAccessFS.ALL,
"/lib": LandlockAccessFS.ALL,
"/lib64": LandlockAccessFS.ALL,
"/flag": LandlockAccessFS.READ_FILE,
})
try:
os.open("/flag", os.O_RDONLY | os.O_CLOEXEC)
except PermissionError:
assert False, "Failed to open /flag for reading"
# Can't write /flag
try:
os.open("/flag", os.O_RDWR | os.O_CLOEXEC)
except PermissionError:
pass
else:
assert False, "Opened /flag for writing"
try:
os.open("/etc/passwd", os.O_RDONLY | os.O_CLOEXEC)
except PermissionError:
pass
else:
assert False, "Opened /etc/passwd for reading"
# Allow full access to /bin, /lib, /lib64
# Nothing else (including /flag now) is allowed
landlock({
"/bin": LandlockAccessFS.ALL,
"/lib": LandlockAccessFS.ALL,
"/lib64": LandlockAccessFS.ALL,
})
try:
os.open("/flag", os.O_RDONLY | os.O_CLOEXEC)
except PermissionError:
pass
else:
assert False, "Opened /flag for reading"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment