Created
January 30, 2024 00:36
-
-
Save ConnorNelson/d7b7202c714730c5edc4ea1819c4bc0f to your computer and use it in GitHub Desktop.
Landlock in Python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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