From c1ab81512d8d0409b1301ae29a08fe5394acb2fc Mon Sep 17 00:00:00 2001 From: Sudipta Pandit Date: Mon, 3 Feb 2025 20:07:39 +0530 Subject: [PATCH] all: add python api for seccomp notify addfd - add python api for seccomp addfd - add tests for the new addfd python api - update receive_notify & respond_notify methods to optionally accpet user provided notification fd --- src/python/libseccomp.pxd | 8 ++ src/python/seccomp.pyx | 175 +++++++++++++++++++++++++++++++++- tests/63-live-notify_addfd.py | 104 ++++++++++++++++++++ tests/Makefile.am | 3 +- 4 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 tests/63-live-notify_addfd.py diff --git a/src/python/libseccomp.pxd b/src/python/libseccomp.pxd index f2784881..ec5b0f70 100644 --- a/src/python/libseccomp.pxd +++ b/src/python/libseccomp.pxd @@ -117,6 +117,13 @@ cdef extern from "seccomp.h": int32_t error uint32_t flags + cdef struct seccomp_notif_addfd: + uint64_t id + uint32_t flags + uint32_t srcfd + uint32_t newfd + uint32_t newfd_flags + scmp_version *seccomp_version() unsigned int seccomp_api_get() @@ -165,6 +172,7 @@ cdef extern from "seccomp.h": void seccomp_notify_free(seccomp_notif *req, seccomp_notif_resp *resp) int seccomp_notify_receive(int fd, seccomp_notif *req) int seccomp_notify_respond(int fd, seccomp_notif_resp *resp) + int seccomp_notify_addfd(int fd, seccomp_notif_addfd *addfd) int seccomp_notify_id_valid(int fd, uint64_t id) int seccomp_notify_fd(scmp_filter_ctx ctx) diff --git a/src/python/seccomp.pyx b/src/python/seccomp.pyx index 7e03dc0e..1a0ccf39 100644 --- a/src/python/seccomp.pyx +++ b/src/python/seccomp.pyx @@ -591,6 +591,139 @@ cdef class NotificationResponse: """ self._flags = value +cdef class NotificationAddfd: + """ Python object representing a seccomp notification addfd structure. + """ + cdef uint64_t _id + cdef uint32_t _flags + cdef uint32_t _srcfd + cdef uint32_t _newfd + cdef uint32_t _newfd_flags + + def __cinit__(self, notify, flags, srcfd, newfd = 0, newflags = 0): + """ Initialize the notification addfd structure. + + Arguments: + notify - a Notification object + srcfd - the source file descriptor + flags - notify addfd flags + newfd - 0 or desired file descriptor number in target + newflags - new flags to set on the target file descriptor + + Description: + Create a seccomp NotificationAddfd object. + """ + self._id = notify.id + self._flags = flags + self._srcfd = srcfd + self._newfd = newfd + self._newfd_flags = newflags + + @property + def id(self): + """ Get the seccomp notification request ID. + + Description: + Get the seccomp notification request ID. + """ + return self._id + + @id.setter + def id(self, value): + """ Set the seccomp notification request ID. + + Arguments: + id - the seccomp notification request ID + + Description: + Set the seccomp notification request ID. + """ + self._id = value + + @property + def flags(self): + """ Get the seccomp notification addfd flags. + + Description: + Get the seccomp notification addfd flags. + """ + return self._flags + + @flags.setter + def flags(self, value): + """ Set the seccomp notification addfd flags. + + Arguments: + flags - the notification addfd flags + + Description: + Set the seccomp notification addfd flags. + """ + self._flags = value + + @property + def srcfd(self): + """ Get the local file descriptor number. + + Description: + Get the local file descriptor number. + """ + return self._srcfd + + @srcfd.setter + def srcfd(self, value): + """ Set the local file descriptor number. + + Arguments: + srcfd - the local file descriptor number + + Description: + Set the local file descriptor number. + """ + self._srcfd = value + + @property + def newfd(self): + """ Get the target file descriptor number. + + Description: + Get the target file descriptor number. + """ + return self._newfd + + @newfd.setter + def newfd(self, value): + """ Set the target file descriptor number. + + Arguments: + newfd - the target file descriptor number + + Description: + Set the target file descriptor number. + """ + self._newfd = value + + @property + def newflags(self): + """ Get the new flags to set on the target file descriptor. + + Description: + Get the new flags to set on the target file descriptor. + """ + return self._newfd_flags + + @newflags.setter + def newflags(self, value): + """ Set the new flags to set on the target file descriptor. + + Arguments: + newflags - the new flags to set on the target file descriptor + + Description: + Set the new flags to set on the target file descriptor. + """ + self._newfd_flags = value + cdef class SyscallFilter: """ Python object representing a seccomp syscall filter. """ cdef int _defaction @@ -959,16 +1092,20 @@ cdef class SyscallFilter: if rc != 0: raise RuntimeError(str.format("Library error (errno = {0})", rc)) - def receive_notify(self): + def receive_notify(self, fd = None): """ Receive seccomp notifications. + Arguments: + fd - the notify file descriptor + Description: Receive a seccomp notification from the system, requires the use of the NOTIFY action. """ cdef libseccomp.seccomp_notif *req - fd = libseccomp.seccomp_notify_fd(self._ctx) + if fd is None: + fd = libseccomp.seccomp_notify_fd(self._ctx) if fd < 0: raise RuntimeError("Notifications not enabled/active") rc = libseccomp.seccomp_notify_alloc(&req, NULL) @@ -988,18 +1125,20 @@ cdef class SyscallFilter: free(req) return notify - def respond_notify(self, response): + def respond_notify(self, response, fd = None): """ Send a seccomp notification response. Arguments: response - the response to send to the system + fd - the notify file descriptor Description: Respond to a seccomp notification. """ cdef libseccomp.seccomp_notif_resp *resp - fd = libseccomp.seccomp_notify_fd(self._ctx) + if fd is None: + fd = libseccomp.seccomp_notify_fd(self._ctx) if fd < 0: raise RuntimeError("Notifications not enabled/active") rc = libseccomp.seccomp_notify_alloc(NULL, &resp) @@ -1026,6 +1165,34 @@ cdef class SyscallFilter: raise RuntimeError("Notifications not enabled/active") return fd + def notify_addfd(self, addfd_obj, fd = None): + """Add a file descriptor to supervisee + + Arguments: + addfd_obj - the addfd object + fd - the notify file descriptor + + Description: + Add a file descriptor to the supervisee process. + """ + if fd is None: + fd = libseccomp.seccomp_notify_fd(self._ctx) + if fd < 0: + raise RuntimeError("Notifications not enabled/active") + + cdef libseccomp.seccomp_notif_addfd addfd + + addfd.id = addfd_obj.id + addfd.flags = addfd_obj.flags + addfd.srcfd = addfd_obj.srcfd + addfd.newfd = addfd_obj.newfd + addfd.newfd_flags = addfd_obj.newflags + + rc = libseccomp.seccomp_notify_addfd(fd, &addfd) + if rc < 0: + raise RuntimeError(str.format("Library error (errno = {0})", rc)) + return rc + def export_pfc(self, file): """ Export the filter in PFC format. diff --git a/tests/63-live-notify_addfd.py b/tests/63-live-notify_addfd.py new file mode 100644 index 00000000..6e5f3301 --- /dev/null +++ b/tests/63-live-notify_addfd.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +# +# Seccomp Library test program +# +# Copyright (c) 2025 Microsoft Corporation +# Author: Sudipta Pandit + + +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of version 2.1 of the GNU Lesser General Public License as +# published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, see . +# + +import argparse +import ctypes +import ctypes.util +import os +import struct +import socket + +import util + +from seccomp import * + + +def send_fd(sock: socket.socket, fd: int): + sock.sendmsg([b' '], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, struct.pack('i', fd))]) + +def recv_fd(sock: socket.socket): + _msg, ancdata, _flags, _addr = sock.recvmsg(1, socket.CMSG_LEN(struct.calcsize('i'))) + for cmsg_level, cmsg_type, cmsg_data in ancdata: + if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS: + return struct.unpack('i', cmsg_data)[0] + return None + +def test(): + f = SyscallFilter(ALLOW) + f.add_rule(NOTIFY, "openat") + + # Socketpair for sending file descriptors + p_socket, c_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM) + + pid = os.fork() + if pid == 0: + # load seccomp filter + f.load() + notify_fd = f.get_notify_fd() + send_fd(c_socket, notify_fd) + + ret_fd = os.open("/etc/hostname", os.O_RDONLY) + if ret_fd < 0: + # raise RuntimeError("Response return value failed") + quit(ret_fd) + + ret_bytes = os.read(ret_fd, 128) + os.close(ret_fd) + if len(ret_bytes) != 0: + # /dev/null should be empty + quit(1) + + os.close(notify_fd) + c_socket.close() + quit(0) + else: + # get the notification fd from child + notify_fd = recv_fd(p_socket) + notify = f.receive_notify(fd=notify_fd) + + if notify.syscall != resolve_syscall(Arch(), "openat"): + raise RuntimeError("Notification failed") + + new_fd = os.open("/dev/null", os.O_RDONLY) + # print("New fd", new_fd) + installed_fd = f.notify_addfd(NotificationAddfd(notify, 0, new_fd), fd=notify_fd) + # print("Installed fd", installed_fd) + f.respond_notify(NotificationResponse(notify, installed_fd, 0, 0), fd=notify_fd) + + # No longer need the fds + os.close(new_fd) + os.close(notify_fd) + p_socket.close() + + wpid, rc = os.waitpid(pid, 0) + if os.WIFEXITED(rc) == 0: + raise RuntimeError("Child process error") + if os.WEXITSTATUS(rc) != 0: + raise RuntimeError("Child process error") + + quit(160) + +test() + +# kate: syntax python; +# kate: indent-mode python; space-indent on; indent-width 4; mixedindent off; diff --git a/tests/Makefile.am b/tests/Makefile.am index 7b1bb8cf..70d06105 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -161,7 +161,8 @@ EXTRA_DIST_TESTPYTHON = \ 59-basic-empty_binary_tree.py \ 60-sim-precompute.py \ 61-sim-transactions.py \ - 62-sim-arch_transactions.py + 62-sim-arch_transactions.py \ + 63-live-notify_addfd.py EXTRA_DIST_TESTCFGS = \ 01-sim-allow.tests \