Skip to content

Commit 4188036

Browse files
committed
tests: add test 63-live-notify_addfd
Add live test for seccomp_notify_addfd() that verifies fd injection capabilities by intercepting openat() syscalls and replacing the target's file descriptor for it's C and Python API. Signed-off-by: Sudipta Pandit <sudpandit@microsoft.com>
1 parent 2808909 commit 4188036

File tree

5 files changed

+355
-3
lines changed

5 files changed

+355
-3
lines changed

tests/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,4 @@ util.pyc
7070
60-sim-precompute
7171
61-sim-transactions
7272
62-sim-arch_transactions
73+
63-live-notify_addfd

tests/63-live-notify_addfd.c

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/**
2+
* Seccomp Library test program
3+
*
4+
* Copyright (c) 2025 Microsoft Corporation <sudpandit@microsoft.com>
5+
* Author: Sudipta Pandit <sudpandit@microsoft.com>
6+
*/
7+
8+
/*
9+
* This library is free software; you can redistribute it and/or modify it
10+
* under the terms of version 2.1 of the GNU Lesser General Public License as
11+
* published by the Free Software Foundation.
12+
*
13+
* This library is distributed in the hope that it will be useful, but WITHOUT
14+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16+
* for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public License
19+
* along with this library; if not, see <http://www.gnu.org/licenses>.
20+
*/
21+
22+
#include <errno.h>
23+
#include <fcntl.h>
24+
#include <signal.h>
25+
#include <seccomp.h>
26+
#include <string.h>
27+
#include <sys/socket.h>
28+
#include <sys/wait.h>
29+
#include <unistd.h>
30+
31+
32+
int send_fd(int sock, int fd)
33+
{
34+
struct iovec iov = {.iov_base = "F", .iov_len = 1};
35+
char buffer[CMSG_SPACE(sizeof(fd))];
36+
memset(buffer, 0, sizeof(buffer));
37+
38+
struct msghdr msg = {
39+
.msg_iov = &iov,
40+
.msg_iovlen = 1,
41+
.msg_control = buffer,
42+
.msg_controllen = sizeof(buffer)
43+
};
44+
45+
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
46+
cmsg->cmsg_level = SOL_SOCKET;
47+
cmsg->cmsg_type = SCM_RIGHTS;
48+
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
49+
50+
memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
51+
52+
return sendmsg(sock, &msg, 0);
53+
}
54+
55+
int recv_fd(int sock)
56+
{
57+
char m_buffer[1];
58+
struct iovec iov = {.iov_base = m_buffer, .iov_len = 1};
59+
60+
char buffer[CMSG_SPACE(sizeof(int))];
61+
struct msghdr msg = {
62+
.msg_iov = &iov,
63+
.msg_iovlen = 1,
64+
.msg_control = buffer,
65+
.msg_controllen = sizeof(buffer)
66+
};
67+
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
68+
69+
if (recvmsg(sock, &msg, 0) < 0)
70+
return -1;
71+
72+
int fd;
73+
memcpy(&fd, CMSG_DATA(cmsg), sizeof(fd));
74+
return fd;
75+
}
76+
77+
void child_process(scmp_filter_ctx ctx, int sock_fd)
78+
{
79+
int rc;
80+
int ret = -1;
81+
int notify_fd = -1;
82+
char buf[128];
83+
ssize_t bytes_read = -1;
84+
85+
rc = seccomp_load(ctx);
86+
if (rc < 0)
87+
goto out;
88+
89+
rc = seccomp_notify_fd(ctx);
90+
if (rc < 0)
91+
goto out;
92+
notify_fd = rc;
93+
94+
rc = send_fd(sock_fd, notify_fd);
95+
if (rc < 0) {
96+
rc = -errno;
97+
goto out;
98+
}
99+
100+
ret = openat(AT_FDCWD, "/etc/hostname", O_RDONLY);
101+
if (ret < 0) {
102+
rc = -errno;
103+
goto out;
104+
}
105+
106+
bytes_read = read(ret, buf, sizeof(buf));
107+
rc = bytes_read;
108+
109+
out:
110+
if (notify_fd >= 0)
111+
close(notify_fd);
112+
if (ret >= 0)
113+
close(ret);
114+
close(sock_fd);
115+
exit(rc);
116+
}
117+
118+
int parent_process(int sock_fd)
119+
{
120+
int rc;
121+
int notify_fd = -1;
122+
int new_fd = -1;
123+
int installed_fd = -1;
124+
struct seccomp_notif *req = NULL;
125+
struct seccomp_notif_resp *resp = NULL;
126+
struct seccomp_notif_addfd addfd = {0};
127+
128+
rc = recv_fd(sock_fd);
129+
if (rc < 0) {
130+
rc = -errno;
131+
goto out;
132+
}
133+
notify_fd = rc;
134+
135+
rc = seccomp_notify_alloc(&req, &resp);
136+
if (rc)
137+
goto out;
138+
139+
rc = seccomp_notify_receive(notify_fd, req);
140+
if (rc)
141+
goto out;
142+
if (req->data.nr != __NR_openat) {
143+
rc = -EFAULT;
144+
goto out;
145+
}
146+
147+
new_fd = openat(AT_FDCWD, "/dev/null", O_RDONLY);
148+
if (new_fd < 0) {
149+
rc = -errno;
150+
goto out;
151+
}
152+
153+
memset(&addfd, 0, sizeof(addfd));
154+
addfd.id = req->id;
155+
addfd.srcfd = new_fd;
156+
addfd.newfd = 0;
157+
addfd.flags = 0;
158+
rc = seccomp_notify_addfd(notify_fd, &addfd);
159+
if (rc < 0)
160+
goto out;
161+
installed_fd = rc;
162+
163+
rc = seccomp_notify_id_valid(notify_fd, req->id);
164+
if (rc)
165+
goto out;
166+
167+
resp->id = req->id;
168+
resp->val = installed_fd;
169+
resp->error = 0;
170+
resp->flags = 0;
171+
rc = seccomp_notify_respond(notify_fd, resp);
172+
173+
out:
174+
if (notify_fd >= 0)
175+
close(notify_fd);
176+
if (new_fd >= 0)
177+
close(new_fd);
178+
close(sock_fd);
179+
seccomp_notify_free(req, resp);
180+
return rc;
181+
}
182+
183+
int main(int argc, char *argv[])
184+
{
185+
int rc, status;
186+
int sock_pair[2];
187+
scmp_filter_ctx ctx = NULL;
188+
pid_t pid = 0;
189+
190+
ctx = seccomp_init(SCMP_ACT_ALLOW);
191+
if (ctx == NULL)
192+
return ENOMEM;
193+
194+
rc = seccomp_rule_add(ctx, SCMP_ACT_NOTIFY, SCMP_SYS(openat), 0, NULL);
195+
if (rc)
196+
goto out;
197+
198+
/* set up socket pair for sending notify_fd */
199+
rc = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sock_pair);
200+
if (rc < 0) {
201+
rc = -errno;
202+
goto out;
203+
}
204+
205+
pid = fork();
206+
if (pid == 0) {
207+
close(sock_pair[0]); /* close the parent's end */
208+
child_process(ctx, sock_pair[1]);
209+
} else {
210+
close(sock_pair[1]); /* close the child's end */
211+
rc = parent_process(sock_pair[0]);
212+
213+
if (waitpid(pid, &status, 0) != pid) {
214+
rc = -EFAULT;
215+
goto out;
216+
}
217+
218+
if (!WIFEXITED(status)) {
219+
rc = -EFAULT;
220+
goto out;
221+
}
222+
if (WEXITSTATUS(status)) {
223+
rc = -EFAULT;
224+
goto out;
225+
}
226+
}
227+
228+
out:
229+
if (pid)
230+
kill(pid, SIGKILL);
231+
seccomp_release(ctx);
232+
233+
if (rc != 0)
234+
return (rc < 0 ? -rc : rc);
235+
return 160;
236+
}

tests/63-live-notify_addfd.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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+
quit(ret_fd)
63+
64+
ret_bytes = os.read(ret_fd, 128)
65+
os.close(ret_fd)
66+
if len(ret_bytes) != 0:
67+
# /dev/null should be empty
68+
quit(1)
69+
70+
os.close(notify_fd)
71+
c_socket.close()
72+
quit(0)
73+
else:
74+
# get the notification fd from child
75+
notify_fd = recv_fd(p_socket)
76+
notify = f.receive_notify(fd=notify_fd)
77+
78+
if notify.syscall != resolve_syscall(Arch(), "openat"):
79+
raise RuntimeError("Notification failed")
80+
81+
new_fd = os.open("/dev/null", os.O_RDONLY)
82+
installed_fd = f.notify_addfd(NotificationAddfd(notify, 0, new_fd), fd=notify_fd)
83+
f.respond_notify(NotificationResponse(notify, installed_fd, 0, 0), fd=notify_fd)
84+
85+
# No longer need the fds
86+
os.close(new_fd)
87+
os.close(notify_fd)
88+
p_socket.close()
89+
90+
wpid, rc = os.waitpid(pid, 0)
91+
if os.WIFEXITED(rc) == 0:
92+
raise RuntimeError("Child process error")
93+
if os.WEXITSTATUS(rc) != 0:
94+
raise RuntimeError("Child process error")
95+
96+
quit(160)
97+
98+
test()
99+
100+
# kate: syntax python;
101+
# kate: indent-mode python; space-indent on; indent-width 4; mixedindent off;

tests/63-live-notify_addfd.tests

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#
2+
# libseccomp regression test automation data
3+
#
4+
# Copyright (c) 2025 Microsoft Corporation <sudpandit@microsoft.com>
5+
# Author: Sudipta Pandit <sudpandit@microsoft.com>
6+
#
7+
8+
test type: live
9+
10+
# Testname API Result
11+
63-live-notify_addfd 5 ALLOW

tests/Makefile.am

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ check_PROGRAMS = \
9797
59-basic-empty_binary_tree \
9898
60-sim-precompute \
9999
61-sim-transactions \
100-
62-sim-arch_transactions
100+
62-sim-arch_transactions \
101+
63-live-notify_addfd
101102

102103
EXTRA_DIST_TESTPYTHON = \
103104
util.py \
@@ -160,7 +161,8 @@ EXTRA_DIST_TESTPYTHON = \
160161
59-basic-empty_binary_tree.py \
161162
60-sim-precompute.py \
162163
61-sim-transactions.py \
163-
62-sim-arch_transactions.py
164+
62-sim-arch_transactions.py \
165+
63-live-notify_addfd.py
164166

165167
EXTRA_DIST_TESTCFGS = \
166168
01-sim-allow.tests \
@@ -224,7 +226,8 @@ EXTRA_DIST_TESTCFGS = \
224226
59-basic-empty_binary_tree.tests \
225227
60-sim-precompute.tests \
226228
61-sim-transactions.tests \
227-
62-sim-arch_transactions.tests
229+
62-sim-arch_transactions.tests \
230+
63-live-notify_addfd.tests
228231

229232
EXTRA_DIST_TESTSCRIPTS = \
230233
38-basic-pfc_coverage.sh 38-basic-pfc_coverage.pfc \

0 commit comments

Comments
 (0)