Skip to content

Commit

Permalink
all: add python api for seccomp notify addfd
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
realsdx committed Feb 3, 2025
1 parent 38b9974 commit c1ab815
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 5 deletions.
8 changes: 8 additions & 0 deletions src/python/libseccomp.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)

Expand Down
175 changes: 171 additions & 4 deletions src/python/seccomp.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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.
Expand Down
104 changes: 104 additions & 0 deletions tests/63-live-notify_addfd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python

#
# Seccomp Library test program
#
# Copyright (c) 2025 Microsoft Corporation <sudpandit@microsoft.com>
# Author: Sudipta Pandit <sudpandit@microsoft.com>


#
# 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 <http://www.gnu.org/licenses>.
#

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;
3 changes: 2 additions & 1 deletion tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down

0 comments on commit c1ab815

Please sign in to comment.