Skip to content

Commit c1ab815

Browse files
committed
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
1 parent 38b9974 commit c1ab815

File tree

4 files changed

+285
-5
lines changed

4 files changed

+285
-5
lines changed

src/python/libseccomp.pxd

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ cdef extern from "seccomp.h":
117117
int32_t error
118118
uint32_t flags
119119

120+
cdef struct seccomp_notif_addfd:
121+
uint64_t id
122+
uint32_t flags
123+
uint32_t srcfd
124+
uint32_t newfd
125+
uint32_t newfd_flags
126+
120127
scmp_version *seccomp_version()
121128

122129
unsigned int seccomp_api_get()
@@ -165,6 +172,7 @@ cdef extern from "seccomp.h":
165172
void seccomp_notify_free(seccomp_notif *req, seccomp_notif_resp *resp)
166173
int seccomp_notify_receive(int fd, seccomp_notif *req)
167174
int seccomp_notify_respond(int fd, seccomp_notif_resp *resp)
175+
int seccomp_notify_addfd(int fd, seccomp_notif_addfd *addfd)
168176
int seccomp_notify_id_valid(int fd, uint64_t id)
169177
int seccomp_notify_fd(scmp_filter_ctx ctx)
170178

src/python/seccomp.pyx

Lines changed: 171 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,139 @@ cdef class NotificationResponse:
591591
"""
592592
self._flags = value
593593

594+
cdef class NotificationAddfd:
595+
""" Python object representing a seccomp notification addfd structure.
596+
"""
597+
cdef uint64_t _id
598+
cdef uint32_t _flags
599+
cdef uint32_t _srcfd
600+
cdef uint32_t _newfd
601+
cdef uint32_t _newfd_flags
602+
603+
def __cinit__(self, notify, flags, srcfd, newfd = 0, newflags = 0):
604+
""" Initialize the notification addfd structure.
605+
606+
Arguments:
607+
notify - a Notification object
608+
srcfd - the source file descriptor
609+
flags - notify addfd flags
610+
newfd - 0 or desired file descriptor number in target
611+
newflags - new flags to set on the target file descriptor
612+
613+
Description:
614+
Create a seccomp NotificationAddfd object.
615+
"""
616+
self._id = notify.id
617+
self._flags = flags
618+
self._srcfd = srcfd
619+
self._newfd = newfd
620+
self._newfd_flags = newflags
621+
622+
@property
623+
def id(self):
624+
""" Get the seccomp notification request ID.
625+
626+
Description:
627+
Get the seccomp notification request ID.
628+
"""
629+
return self._id
630+
631+
@id.setter
632+
def id(self, value):
633+
""" Set the seccomp notification request ID.
634+
635+
Arguments:
636+
id - the seccomp notification request ID
637+
638+
Description:
639+
Set the seccomp notification request ID.
640+
"""
641+
self._id = value
642+
643+
@property
644+
def flags(self):
645+
""" Get the seccomp notification addfd flags.
646+
647+
Description:
648+
Get the seccomp notification addfd flags.
649+
"""
650+
return self._flags
651+
652+
@flags.setter
653+
def flags(self, value):
654+
""" Set the seccomp notification addfd flags.
655+
656+
Arguments:
657+
flags - the notification addfd flags
658+
659+
Description:
660+
Set the seccomp notification addfd flags.
661+
"""
662+
self._flags = value
663+
664+
@property
665+
def srcfd(self):
666+
""" Get the local file descriptor number.
667+
668+
Description:
669+
Get the local file descriptor number.
670+
"""
671+
return self._srcfd
672+
673+
@srcfd.setter
674+
def srcfd(self, value):
675+
""" Set the local file descriptor number.
676+
677+
Arguments:
678+
srcfd - the local file descriptor number
679+
680+
Description:
681+
Set the local file descriptor number.
682+
"""
683+
self._srcfd = value
684+
685+
@property
686+
def newfd(self):
687+
""" Get the target file descriptor number.
688+
689+
Description:
690+
Get the target file descriptor number.
691+
"""
692+
return self._newfd
693+
694+
@newfd.setter
695+
def newfd(self, value):
696+
""" Set the target file descriptor number.
697+
698+
Arguments:
699+
newfd - the target file descriptor number
700+
701+
Description:
702+
Set the target file descriptor number.
703+
"""
704+
self._newfd = value
705+
706+
@property
707+
def newflags(self):
708+
""" Get the new flags to set on the target file descriptor.
709+
710+
Description:
711+
Get the new flags to set on the target file descriptor.
712+
"""
713+
return self._newfd_flags
714+
715+
@newflags.setter
716+
def newflags(self, value):
717+
""" Set the new flags to set on the target file descriptor.
718+
719+
Arguments:
720+
newflags - the new flags to set on the target file descriptor
721+
722+
Description:
723+
Set the new flags to set on the target file descriptor.
724+
"""
725+
self._newfd_flags = value
726+
594727
cdef class SyscallFilter:
595728
""" Python object representing a seccomp syscall filter. """
596729
cdef int _defaction
@@ -959,16 +1092,20 @@ cdef class SyscallFilter:
9591092
if rc != 0:
9601093
raise RuntimeError(str.format("Library error (errno = {0})", rc))
9611094

962-
def receive_notify(self):
1095+
def receive_notify(self, fd = None):
9631096
""" Receive seccomp notifications.
9641097
1098+
Arguments:
1099+
fd - the notify file descriptor
1100+
9651101
Description:
9661102
Receive a seccomp notification from the system, requires the use of
9671103
the NOTIFY action.
9681104
"""
9691105
cdef libseccomp.seccomp_notif *req
9701106

971-
fd = libseccomp.seccomp_notify_fd(self._ctx)
1107+
if fd is None:
1108+
fd = libseccomp.seccomp_notify_fd(self._ctx)
9721109
if fd < 0:
9731110
raise RuntimeError("Notifications not enabled/active")
9741111
rc = libseccomp.seccomp_notify_alloc(&req, NULL)
@@ -988,18 +1125,20 @@ cdef class SyscallFilter:
9881125
free(req)
9891126
return notify
9901127

991-
def respond_notify(self, response):
1128+
def respond_notify(self, response, fd = None):
9921129
""" Send a seccomp notification response.
9931130
9941131
Arguments:
9951132
response - the response to send to the system
1133+
fd - the notify file descriptor
9961134
9971135
Description:
9981136
Respond to a seccomp notification.
9991137
"""
10001138
cdef libseccomp.seccomp_notif_resp *resp
10011139

1002-
fd = libseccomp.seccomp_notify_fd(self._ctx)
1140+
if fd is None:
1141+
fd = libseccomp.seccomp_notify_fd(self._ctx)
10031142
if fd < 0:
10041143
raise RuntimeError("Notifications not enabled/active")
10051144
rc = libseccomp.seccomp_notify_alloc(NULL, &resp)
@@ -1026,6 +1165,34 @@ cdef class SyscallFilter:
10261165
raise RuntimeError("Notifications not enabled/active")
10271166
return fd
10281167

1168+
def notify_addfd(self, addfd_obj, fd = None):
1169+
"""Add a file descriptor to supervisee
1170+
1171+
Arguments:
1172+
addfd_obj - the addfd object
1173+
fd - the notify file descriptor
1174+
1175+
Description:
1176+
Add a file descriptor to the supervisee process.
1177+
"""
1178+
if fd is None:
1179+
fd = libseccomp.seccomp_notify_fd(self._ctx)
1180+
if fd < 0:
1181+
raise RuntimeError("Notifications not enabled/active")
1182+
1183+
cdef libseccomp.seccomp_notif_addfd addfd
1184+
1185+
addfd.id = addfd_obj.id
1186+
addfd.flags = addfd_obj.flags
1187+
addfd.srcfd = addfd_obj.srcfd
1188+
addfd.newfd = addfd_obj.newfd
1189+
addfd.newfd_flags = addfd_obj.newflags
1190+
1191+
rc = libseccomp.seccomp_notify_addfd(fd, &addfd)
1192+
if rc < 0:
1193+
raise RuntimeError(str.format("Library error (errno = {0})", rc))
1194+
return rc
1195+
10291196
def export_pfc(self, file):
10301197
""" Export the filter in PFC format.
10311198

tests/63-live-notify_addfd.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env python
2+
3+
#
4+
# Seccomp Library test program
5+
#
6+
# Copyright (c) 2025 Microsoft Corporation <sudpandit@microsoft.com>
7+
# Author: Sudipta Pandit <sudpandit@microsoft.com>
8+
9+
10+
#
11+
# This library is free software; you can redistribute it and/or modify it
12+
# under the terms of version 2.1 of the GNU Lesser General Public License as
13+
# published by the Free Software Foundation.
14+
#
15+
# This library is distributed in the hope that it will be useful, but WITHOUT
16+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
18+
# for more details.
19+
#
20+
# You should have received a copy of the GNU Lesser General Public License
21+
# along with this library; if not, see <http://www.gnu.org/licenses>.
22+
#
23+
24+
import argparse
25+
import ctypes
26+
import ctypes.util
27+
import os
28+
import struct
29+
import socket
30+
31+
import util
32+
33+
from seccomp import *
34+
35+
36+
def send_fd(sock: socket.socket, fd: int):
37+
sock.sendmsg([b' '], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, struct.pack('i', fd))])
38+
39+
def recv_fd(sock: socket.socket):
40+
_msg, ancdata, _flags, _addr = sock.recvmsg(1, socket.CMSG_LEN(struct.calcsize('i')))
41+
for cmsg_level, cmsg_type, cmsg_data in ancdata:
42+
if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS:
43+
return struct.unpack('i', cmsg_data)[0]
44+
return None
45+
46+
def test():
47+
f = SyscallFilter(ALLOW)
48+
f.add_rule(NOTIFY, "openat")
49+
50+
# Socketpair for sending file descriptors
51+
p_socket, c_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
52+
53+
pid = os.fork()
54+
if pid == 0:
55+
# load seccomp filter
56+
f.load()
57+
notify_fd = f.get_notify_fd()
58+
send_fd(c_socket, notify_fd)
59+
60+
ret_fd = os.open("/etc/hostname", os.O_RDONLY)
61+
if ret_fd < 0:
62+
# raise RuntimeError("Response return value failed")
63+
quit(ret_fd)
64+
65+
ret_bytes = os.read(ret_fd, 128)
66+
os.close(ret_fd)
67+
if len(ret_bytes) != 0:
68+
# /dev/null should be empty
69+
quit(1)
70+
71+
os.close(notify_fd)
72+
c_socket.close()
73+
quit(0)
74+
else:
75+
# get the notification fd from child
76+
notify_fd = recv_fd(p_socket)
77+
notify = f.receive_notify(fd=notify_fd)
78+
79+
if notify.syscall != resolve_syscall(Arch(), "openat"):
80+
raise RuntimeError("Notification failed")
81+
82+
new_fd = os.open("/dev/null", os.O_RDONLY)
83+
# print("New fd", new_fd)
84+
installed_fd = f.notify_addfd(NotificationAddfd(notify, 0, new_fd), fd=notify_fd)
85+
# print("Installed fd", installed_fd)
86+
f.respond_notify(NotificationResponse(notify, installed_fd, 0, 0), fd=notify_fd)
87+
88+
# No longer need the fds
89+
os.close(new_fd)
90+
os.close(notify_fd)
91+
p_socket.close()
92+
93+
wpid, rc = os.waitpid(pid, 0)
94+
if os.WIFEXITED(rc) == 0:
95+
raise RuntimeError("Child process error")
96+
if os.WEXITSTATUS(rc) != 0:
97+
raise RuntimeError("Child process error")
98+
99+
quit(160)
100+
101+
test()
102+
103+
# kate: syntax python;
104+
# kate: indent-mode python; space-indent on; indent-width 4; mixedindent off;

tests/Makefile.am

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ EXTRA_DIST_TESTPYTHON = \
161161
59-basic-empty_binary_tree.py \
162162
60-sim-precompute.py \
163163
61-sim-transactions.py \
164-
62-sim-arch_transactions.py
164+
62-sim-arch_transactions.py \
165+
63-live-notify_addfd.py
165166

166167
EXTRA_DIST_TESTCFGS = \
167168
01-sim-allow.tests \

0 commit comments

Comments
 (0)