public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [tests/selinux] pr775-checkpolicy-revdeps: Add a test for two-way SCTP association setup
@ 2026-09-11 13:19 Ondrej Mosnacek
0 siblings, 0 replies; 2+ messages in thread
From: Ondrej Mosnacek @ 2026-09-11 13:19 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : tests/selinux
Branch : pr775-checkpolicy-revdeps
Commit : 7b6ee73ecbf37c183dea55e663d717ae469f61bf
Author : Ondrej Mosnacek <omosnace@redhat.com>
Date : 2022-02-13T21:14:49+01:00
Stats : +754/-0 in 7 file(s)
URL : https://src.fedoraproject.org/tests/selinux/c/7b6ee73ecbf37c183dea55e663d717ae469f61bf?branch=pr775-checkpolicy-revdeps
Log:
Add a test for two-way SCTP association setup
This test reproduces a bug that may cause an AVC with sctp_socket,
association, unlabeled_t, unlabeled_t (and also another bug related to
SCTP peeloff on the client side).
SCTP code taken and modified from selinux-testsuite's sctp subtest.
Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
---
diff --git a/kernel/sctp_peer_label_bug/Makefile b/kernel/sctp_peer_label_bug/Makefile
new file mode 100644
index 0000000..30c86c8
--- /dev/null
+++ b/kernel/sctp_peer_label_bug/Makefile
@@ -0,0 +1,8 @@
+# Minimal Makefile for standard-test-roles-beakerlib
+
+R="Requires: kernel-modules-extra"
+R="RhtsRequires: make gcc lksctp-tools-devel audit kernel-modules"
+
+run:
+ chmod +x runtest.sh
+ ./runtest.sh
diff --git a/kernel/sctp_peer_label_bug/main.fmf b/kernel/sctp_peer_label_bug/main.fmf
new file mode 100644
index 0000000..ae87830
--- /dev/null
+++ b/kernel/sctp_peer_label_bug/main.fmf
@@ -0,0 +1,25 @@
+summary: Verify that two-way SCTP association setup doesn't trigger AVCs
+description: |
+ Verify that two-way SCTP association setup doesn't trigger AVCs.
+contact: Ondrej Mosnacek <omosnace@redhat.com>
+component:
+ - kernel
+framework: beakerlib
+require:
+ - make
+ - gcc
+ - lksctp-tools-devel
+ - audit
+ - kernel-modules
+recommend:
+ - kernel-modules-extra
+duration: 10m
+tier: 2
+enabled: true
+adjust:
+ - enabled: false
+ when: distro < rhel-8
+ because: RHEL-7 and below don't have SCTP SELinux support
+link:
+ - verifies: https://bugzilla.redhat.com/show_bug.cgi?id=2048251
+ - verifies: https://bugzilla.redhat.com/show_bug.cgi?id=2015525
diff --git a/kernel/sctp_peer_label_bug/runtest.sh b/kernel/sctp_peer_label_bug/runtest.sh
new file mode 100755
index 0000000..22d2150
--- /dev/null
+++ b/kernel/sctp_peer_label_bug/runtest.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+# vim: dict=/usr/share/beakerlib/dictionary.vim cpt=.,w,b,u,t,i,k
+# SPDX-License-Identifier: GPLv2
+# Copyright (c) 2022 Red Hat, Inc.
+# Author: Ondrej Mosnacek <omosnace@redhat.com>
+
+# Include Beakerlib environment
+. /usr/share/beakerlib/beakerlib.sh || exit 1
+
+function check_avc_begin() {
+ sleep 1.1
+ date +'%x %T'
+ sleep 1.1
+}
+
+function check_avc_end() {
+ marker="marker-$RANDOM"
+
+ auditctl -m "$marker"
+
+ for (( i = 0; i < 100; i++ )); do
+ if ausearch -i -m user -ts $1 2>/dev/null </dev/null | \
+ grep -q "$marker"
+ then
+ echo $i
+ break
+ fi
+ sleep 0.1
+ done
+
+ ausearch -i -m avc -ts $1 </dev/null
+}
+
+rlJournalStart
+ rlPhaseStartSetup
+ rlRun "uname -r" 0 "Print kernel version"
+
+ for prog in sctp_bz2048251_client sctp_bz2048251_server; do
+ rlRun "gcc -O2 -Wall -o $prog sctp_common.c $prog.c -lsctp" 0 \
+ "Compile $prog"
+ done
+ rlRun "modprobe sctp" 0 "Ensure SCTP module is loaded"
+ rlRun "mkfifo flag" 0 "Create a fifo file for test"
+ rlPhaseEnd
+
+ rlPhaseStartTest
+ rlRun "audit_ts=\"\$(check_avc_begin)\""
+
+ rlRun "./sctp_bz2048251_server -f flag -4 9999 &" 0 "Start the server"
+ rlRun "read -t 5 <>flag" 0 "Wait for the server to start listening"
+ rlRun "./sctp_bz2048251_client 127.0.0.1 9999" 0 "Run the client"
+ rlRun "wait" 0 "Wait for the server to exit"
+
+ rlRun "check_avc_end \"\$audit_ts\"" 1 "Check if there are AVC denials"
+ rlPhaseEnd
+
+ rlPhaseStartCleanup
+ rlRun "rm -f sctp_bz2048251_client sctp_bz2048251_server flag"
+ rlPhaseEnd
+rlJournalPrintText
+rlJournalEnd
diff --git a/kernel/sctp_peer_label_bug/sctp_bz2048251_client.c b/kernel/sctp_peer_label_bug/sctp_bz2048251_client.c
new file mode 100644
index 0000000..03fbd09
--- /dev/null
+++ b/kernel/sctp_peer_label_bug/sctp_bz2048251_client.c
@@ -0,0 +1,109 @@
+#include "sctp_common.h"
+
+static void usage(char *progname)
+{
+ fprintf(stderr,
+ "usage: %s [-v] addr port\n"
+ "\nWhere:\n\t"
+
+ "-v Print context and ip options information.\n\t"
+ "addr IPv4 or IPv6 address (e.g. 127.0.0.1 or ::1).\n\t"
+ "port Port for accessing server.\n", progname);
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ int opt, sock, result;
+ struct addrinfo hints, *serverinfo;
+ bool verbose = false;
+ struct timeval tm;
+ socklen_t sinlen;
+ struct sockaddr_storage sin;
+
+ while ((opt = getopt(argc, argv, "v")) != -1) {
+ switch (opt) {
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if ((argc - optind) != 2)
+ usage(argv[0]);
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_protocol = IPPROTO_SCTP;
+ hints.ai_socktype = SOCK_SEQPACKET;
+
+ result = getaddrinfo(argv[optind], argv[optind + 1], &hints,
+ &serverinfo);
+ if (result < 0) {
+ fprintf(stderr, "Client getaddrinfo: %s\n",
+ gai_strerror(result));
+ exit(2);
+ }
+
+ sock = socket(serverinfo->ai_family, serverinfo->ai_socktype,
+ serverinfo->ai_protocol);
+ if (sock < 0) {
+ perror("Client socket");
+ exit(3);
+ }
+
+ /*
+ * These timeouts are set to test whether the peer { recv } completes
+ * or not when the permission is denied.
+ */
+ tm.tv_sec = 4;
+ tm.tv_usec = 0;
+ result = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tm, sizeof(tm));
+ if (result < 0) {
+ perror("Client setsockopt: SO_SNDTIMEO");
+ exit(4);
+ }
+
+ result = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm));
+ if (result < 0) {
+ perror("Client setsockopt: SO_RCVTIMEO");
+ exit(5);
+ }
+
+ if (listen(sock, SOMAXCONN)) {
+ perror("Client listen");
+ close(sock);
+ exit(1);
+ }
+
+ /* Subscribe to assoc_id events */
+ result = set_subscr_events(sock, off, on, off, off);
+ if (result < 0) {
+ perror("Client setsockopt: SCTP_EVENTS");
+ return 1;
+ }
+
+ result = open_assoc(sock, serverinfo->ai_addr, serverinfo->ai_addrlen,
+ verbose);
+ if (result) {
+ close(sock);
+ exit(result);
+ }
+
+ result = receive_assoc(sock, &sin, &sinlen, verbose);
+ if (result) {
+ close(sock);
+ exit(result);
+ }
+
+ result = open_assoc(sock, serverinfo->ai_addr, serverinfo->ai_addrlen,
+ verbose);
+ if (result) {
+ close(sock);
+ exit(result);
+ }
+
+ close(sock);
+ exit(0);
+}
diff --git a/kernel/sctp_peer_label_bug/sctp_bz2048251_server.c b/kernel/sctp_peer_label_bug/sctp_bz2048251_server.c
new file mode 100644
index 0000000..bf47257
--- /dev/null
+++ b/kernel/sctp_peer_label_bug/sctp_bz2048251_server.c
@@ -0,0 +1,132 @@
+#include "sctp_common.h"
+
+static void usage(char *progname)
+{
+ fprintf(stderr,
+ "usage: %s [-4] [-f file] [-v] port\n"
+ "\nWhere:\n\t"
+ "-4 Listen on IPv4 addresses only.\n\t"
+ "-f Write a line to the file when listening starts.\n\t"
+ " \"nopeer\" message to client, otherwise the peer context\n\t"
+ " will be retrieved and sent to client.\n\t"
+ "-v Print context and ip options information.\n\t"
+ "port Listening port.\n", progname);
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ int opt, sock, result;
+ socklen_t sinlen;
+ struct sockaddr_storage sin;
+ struct addrinfo hints, *res;
+ char *flag_file = NULL;
+ bool verbose = false, ipv4 = false;
+ unsigned short port;
+
+ while ((opt = getopt(argc, argv, "4f:v")) != -1) {
+ switch (opt) {
+ case '4':
+ ipv4 = true;
+ break;
+ case 'f':
+ flag_file = optarg;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if ((argc - optind) != 1)
+ usage(argv[0]);
+
+ port = atoi(argv[optind]);
+ if (!port)
+ usage(argv[0]);
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_protocol = IPPROTO_SCTP;
+
+ if (ipv4)
+ hints.ai_family = AF_INET;
+ else
+ hints.ai_family = AF_INET6;
+
+ /* sctp_peeloff(3) must be from 1 to Many style socket */
+ hints.ai_socktype = SOCK_SEQPACKET;
+
+ result = getaddrinfo(NULL, argv[optind], &hints, &res);
+ if (result < 0) {
+ fprintf(stderr, "Server getaddrinfo: %s\n",
+ gai_strerror(result));
+ exit(1);
+ }
+
+ sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (sock < 0) {
+ perror("Server socket");
+ exit(1);
+ }
+
+ result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+ if (result < 0) {
+ perror("Server setsockopt: SO_REUSEADDR");
+ close(sock);
+ exit(1);
+ }
+
+ result = bind(sock, res->ai_addr, res->ai_addrlen);
+ if (result < 0) {
+ perror("Server bind");
+ close(sock);
+ exit(1);
+ }
+
+ if (listen(sock, SOMAXCONN)) {
+ perror("Server listen");
+ close(sock);
+ exit(1);
+ }
+
+ if (flag_file) {
+ FILE *f = fopen(flag_file, "w");
+ if (!f) {
+ perror("Flag file open");
+ exit(1);
+ }
+ fprintf(f, "listening\n");
+ fclose(f);
+ }
+
+ /* Subscribe to assoc_id events */
+ result = set_subscr_events(sock, off, on, off, off);
+ if (result < 0) {
+ perror("Client setsockopt: SCTP_EVENTS");
+ return 1;
+ }
+
+ result = receive_assoc(sock, &sin, &sinlen, verbose);
+ if (result) {
+ close(sock);
+ exit(result);
+ }
+
+ result = open_assoc(sock, (struct sockaddr *)&sin, sinlen, verbose);
+ if (result) {
+ close(sock);
+ exit(result);
+ }
+
+ result = receive_assoc(sock, &sin, &sinlen, verbose);
+ if (result) {
+ close(sock);
+ exit(result);
+ }
+
+ close(sock);
+ exit(0);
+}
diff --git a/kernel/sctp_peer_label_bug/sctp_common.c b/kernel/sctp_peer_label_bug/sctp_common.c
new file mode 100644
index 0000000..99ab7cb
--- /dev/null
+++ b/kernel/sctp_peer_label_bug/sctp_common.c
@@ -0,0 +1,381 @@
+#include "sctp_common.h"
+
+#define member_size(type, member) sizeof(((type *)0)->member)
+#define sizeof_up_to(type, member) (offsetof(type, member) + member_size(type, member))
+
+void print_addr_info(struct sockaddr *sin, char *text)
+{
+ struct sockaddr_in *addr4;
+ struct sockaddr_in6 *addr6;
+ char addr_str[INET6_ADDRSTRLEN + 1];
+
+ switch (sin->sa_family) {
+ case AF_INET:
+ addr4 = (struct sockaddr_in *)sin;
+ inet_ntop(sin->sa_family,
+ (void *)&addr4->sin_addr,
+ addr_str, INET6_ADDRSTRLEN + 1);
+ printf("%s IPv4 addr %s\n", text, addr_str);
+ break;
+ case AF_INET6:
+ addr6 = (struct sockaddr_in6 *)sin;
+ if (IN6_IS_ADDR_V4MAPPED(&addr6->sin6_addr)) {
+ inet_ntop(AF_INET,
+ (void *)&addr6->sin6_addr.s6_addr32[3],
+ addr_str, INET6_ADDRSTRLEN + 1);
+ printf("%s IPv6->IPv4 MAPPED addr %s\n",
+ text, addr_str);
+ } else if (IN6_IS_ADDR_LINKLOCAL(&addr6->sin6_addr)) {
+ inet_ntop(sin->sa_family,
+ (void *)&addr6->sin6_addr,
+ addr_str, INET6_ADDRSTRLEN + 1);
+ printf("%s IPv6 local link addr %s scope_id %d\n",
+ text, addr_str,
+ ((struct sockaddr_in6 *)addr6)->sin6_scope_id);
+ } else {
+ inet_ntop(sin->sa_family,
+ (void *)&addr6->sin6_addr,
+ addr_str, INET6_ADDRSTRLEN + 1);
+ printf("%s IPv6 addr %s\n", text,
+ addr_str);
+ }
+ break;
+ default:
+ printf("%s Unknown IP family %d\n", text, sin->sa_family);
+ break;
+ }
+}
+
+int set_subscr_events(int fd, int data_io, int assoc, int addr, int shutd)
+{
+ struct sctp_event_subscribe subscr_events;
+
+ memset(&subscr_events, 0, sizeof(subscr_events));
+ subscr_events.sctp_data_io_event = data_io;
+ subscr_events.sctp_association_event = assoc;
+ subscr_events.sctp_address_event = addr;
+ subscr_events.sctp_shutdown_event = shutd;
+
+ /*
+ * Truncate optlen to just the fields we touch to avoid errors when
+ * the uapi headers are newer than the running kernel.
+ */
+ return setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, &subscr_events,
+ sizeof_up_to(struct sctp_event_subscribe,
+ sctp_shutdown_event));
+}
+
+/*
+ * Currently only SCTP_ASSOC_CHANGE, SCTP_PEER_ADDR_CHANGE and
+ * SCTP_SHUTDOWN_EVENT are enabled via set_subscr_events().
+ */
+int handle_event(void *buf, char *cmp_addr, sctp_assoc_t *assoc_id,
+ bool verbose, char *text)
+{
+ union sctp_notification *snp = buf;
+ char addrbuf[INET6_ADDRSTRLEN];
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ const char *ap;
+ struct sctp_paddr_change *spc;
+ struct sctp_assoc_change *sac;
+ struct sctp_remote_error *sre;
+ struct sctp_send_failed *ssf;
+ struct sctp_authkey_event *auth_event;
+
+ switch (snp->sn_header.sn_type) {
+ case SCTP_ASSOC_CHANGE:
+ sac = &snp->sn_assoc_change;
+
+ if (verbose)
+ printf("%s SCTP_ASSOC_CHANGE event for assoc_id: %d ERR: 0x%x\n",
+ text, sac->sac_assoc_id, sac->sac_error);
+
+ if (assoc_id)
+ *assoc_id = sac->sac_assoc_id;
+ break;
+ case SCTP_PEER_ADDR_CHANGE:
+ spc = &snp->sn_paddr_change;
+
+ if (verbose)
+ /*
+ * Not all spc_error codes are errors - linux/sctp.h
+ * (e.g. SCTP_HEARTBEAT_SUCCESS = 0x02)
+ */
+ printf("%s SCTP_PEER_ADDR_CHANGE event for assoc_id: %d ERR: 0x%x\n",
+ text, spc->spc_assoc_id, spc->spc_error);
+
+ if (spc->spc_aaddr.ss_family == AF_INET) {
+ sin = (struct sockaddr_in *) &spc->spc_aaddr;
+ ap = inet_ntop(AF_INET, &sin->sin_addr, addrbuf,
+ INET6_ADDRSTRLEN);
+ } else {
+ sin6 = (struct sockaddr_in6 *) &spc->spc_aaddr;
+ ap = inet_ntop(AF_INET6, &sin6->sin6_addr, addrbuf,
+ INET6_ADDRSTRLEN);
+ }
+ if (verbose) /* Print additional address details */
+ print_addr_info((struct sockaddr *)&spc->spc_aaddr,
+ "Peer Address change:\n\t");
+
+ switch (spc->spc_state) {
+ case SCTP_ADDR_AVAILABLE:
+ if (verbose)
+ printf("\t%s is available\n", text);
+ break;
+ case SCTP_ADDR_UNREACHABLE:
+ if (verbose)
+ printf("\t%s is not available - Error: 0x%x\n",
+ text, spc->spc_error);
+ break;
+ case SCTP_ADDR_REMOVED:
+ if (verbose)
+ printf("\t%s was removed\n", text);
+ break;
+ case SCTP_ADDR_ADDED:
+ if (verbose)
+ printf("\t%s was added\n", text);
+ break;
+ case SCTP_ADDR_MADE_PRIM:
+ if (verbose)
+ printf("\t%s is primary\n", text);
+ if (cmp_addr) {
+ if (!strcmp(ap, cmp_addr)) {
+ if (verbose)
+ printf("\t%s and is now the new primary\n", text);
+
+ return EVENT_ADDR_MATCH;
+ }
+ }
+ break;
+ case SCTP_ADDR_CONFIRMED:
+ if (verbose)
+ printf("\t%s is confirmed\n", text);
+ break;
+ default:
+ if (verbose)
+ printf("%s unknown state: %d\n", text,
+ spc->spc_state);
+ break;
+ }
+ break;
+ case SCTP_SEND_FAILED:
+ ssf = &snp->sn_send_failed;
+
+ if (verbose)
+ printf("%s SCTP_SEND_FAILED event assoc_id: %d ERR: 0x%x\n",
+ text, ssf->ssf_assoc_id, ssf->ssf_error);
+ break;
+ case SCTP_REMOTE_ERROR:
+ sre = &snp->sn_remote_error;
+ if (verbose) /* Error in network byte order - linux/sctp.h */
+ printf("%s SCTP_REMOTE_ERROR event ERR: 0x%x\n",
+ text, ntohs(sre->sre_error));
+ break;
+ case SCTP_SHUTDOWN_EVENT:
+ if (verbose)
+ printf("%s SCTP_SHUTDOWN_EVENT\n", text);
+
+ return EVENT_SHUTDOWN;
+ case SCTP_PARTIAL_DELIVERY_EVENT:
+ if (verbose)
+ printf("%s SCTP_PARTIAL_DELIVERY_EVENT\n", text);
+ break;
+ case SCTP_ADAPTATION_INDICATION:
+ if (verbose)
+ printf("%s SCTP_ADAPTATION_INDICATION event\n", text);
+ break;
+ case SCTP_AUTHENTICATION_INDICATION:
+ auth_event = &snp->sn_authkey_event;
+
+ if (verbose) {
+ printf("%s SCTP_AUTHENTICATION_INDICATION event\n"
+ "\tauth_event->auth_type: 0x%x\n"
+ "\tauth_event->auth_flags: 0x%x\n"
+ "\tauth_event->auth_length: 0x%x\n"
+ "\tauth_event->auth_keynumber: 0x%x\n"
+ "\tauth_event->auth_indication: 0x%x\n"
+ "\tauth_event->auth_assoc_id: %d\n",
+ text, auth_event->auth_type,
+ auth_event->auth_flags,
+ auth_event->auth_length,
+ auth_event->auth_keynumber,
+ auth_event->auth_indication,
+ auth_event->auth_assoc_id);
+ }
+ /* SCTP_AUTH_NO_AUTH defined in linux/sctp.h */
+ if (auth_event->auth_indication == SCTP_AUTH_NO_AUTH)
+ return EVENT_NO_AUTH;
+ break;
+ case SCTP_SENDER_DRY_EVENT:
+ if (verbose)
+ printf("%s SCTP_SENDER_DRY_EVENT\n", text);
+ break;
+ case SCTP_STREAM_RESET_EVENT:
+ if (verbose)
+ printf("%s SCTP_STREAM_RESET_EVENT\n", text);
+ break;
+ case SCTP_ASSOC_RESET_EVENT:
+ if (verbose)
+ printf("%s SCTP_ASSOC_RESET_EVENT\n", text);
+ break;
+ case SCTP_STREAM_CHANGE_EVENT:
+ if (verbose)
+ printf("%s SCTP_STREAM_CHANGE_EVENT\n", text);
+ break;
+ case SCTP_SEND_FAILED_EVENT:
+ if (verbose)
+ printf("%s SCTP_SEND_FAILED_EVENT\n", text);
+ break;
+ default:
+ fprintf(stderr, "%s unknown event: 0x%x\n", text,
+ snp->sn_header.sn_type);
+ break;
+ }
+
+ return EVENT_OK;
+}
+
+int receive_assoc(int sock, struct sockaddr_storage *sin, socklen_t *sinlen,
+ int verbose)
+{
+ int result, peeloff_sk = 0, flags;
+ sctp_assoc_t assoc_id = 0;
+ char *peerlabel, msglabel[256];
+
+ *sinlen = sizeof(*sin);
+ flags = 0;
+
+ result = sctp_recvmsg(sock, msglabel, sizeof(msglabel),
+ (struct sockaddr *)sin, sinlen,
+ NULL, &flags);
+ if (result < 0) {
+ perror("Server sctp_recvmsg-1");
+ return 1;
+ }
+
+ if (verbose)
+ print_addr_info((struct sockaddr *)sin,
+ "Server SEQPACKET recvmsg");
+
+ if (!(flags & MSG_NOTIFICATION) || !(flags & MSG_EOR)) {
+ printf("Invalid sctp_recvmsg response FLAGS: %x\n",
+ flags);
+ return 1;
+ }
+
+ handle_event(msglabel, NULL, &assoc_id,
+ verbose, "Peeloff Server");
+ if (assoc_id <= 0) {
+ printf("Server Invalid association ID: %d\n",
+ assoc_id);
+ return 1;
+ }
+ peeloff_sk = sctp_peeloff(sock, assoc_id);
+ if (peeloff_sk < 0) {
+ perror("Server sctp_peeloff");
+ return 1;
+ }
+ if (verbose) {
+ printf("Server sctp_peeloff(3) on sk: %d with association ID: %d\n",
+ peeloff_sk, assoc_id);
+ }
+
+ /* Now get the client msg on peeloff socket */
+ result = sctp_recvmsg(peeloff_sk, msglabel, sizeof(msglabel),
+ (struct sockaddr *)sin, sinlen,
+ NULL, &flags);
+ if (result < 0) {
+ perror("Server sctp_recvmsg-2");
+ close(peeloff_sk);
+ return 1;
+ }
+
+ if (verbose) {
+ print_addr_info((struct sockaddr *)sin,
+ "Server SEQPACKET peeloff recvmsg");
+ printf("peeloff association ID: %d\n",
+ assoc_id);
+ }
+
+ peerlabel = strdup("nopeer");
+
+ printf("Server PEELOFF peer label: %s\n", peerlabel);
+
+ result = sctp_sendmsg(peeloff_sk, peerlabel,
+ strlen(peerlabel),
+ NULL, 0,
+ 0, 0, 0, 0, 0);
+ if (result < 0) {
+ perror("Server sctp_sendmsg");
+ close(peeloff_sk);
+ close(sock);
+ exit(1);
+ }
+
+ if (verbose)
+ printf("Server PEELOFF sent: %s\n", peerlabel);
+
+ free(peerlabel);
+
+ close(peeloff_sk);
+ return 0;
+}
+
+int open_assoc(int sock, struct sockaddr *sin, socklen_t sinlen,
+ int verbose)
+{
+ int result, peeloff_sk = 0, flags;
+ sctp_assoc_t assoc_id = 0;
+ char byte = 0x41, label[1024];
+
+ result = connect(sock, sin, sinlen);
+ if (result < 0) {
+ perror("Client connect");
+ return 1;
+ }
+
+ /* Get assoc_id for sctp_peeloff() */
+ flags = 0;
+ result = sctp_recvmsg(sock, label, sizeof(label),
+ NULL, 0, NULL, &flags);
+ if (result < 0) {
+ perror("Client sctp_recvmsg-1");
+ return 1;
+ }
+
+ if ((flags & (MSG_NOTIFICATION | MSG_EOR)) != (MSG_NOTIFICATION | MSG_EOR)) {
+ printf("Invalid sctp_recvmsg response FLAGS: %x\n", flags);
+ return 1;
+ }
+ handle_event(label, NULL, &assoc_id, verbose, "Peeloff Client");
+ if (assoc_id <= 0) {
+ printf("Client Invalid association ID: %d\n", assoc_id);
+ return 1;
+ }
+
+ peeloff_sk = sctp_peeloff(sock, assoc_id);
+ if (peeloff_sk < 0) {
+ perror("Client sctp_peeloff");
+ return 1;
+ }
+
+ result = sctp_sendmsg(peeloff_sk, &byte, 1, NULL, 0,
+ 0, 0, 0, 0, 0);
+ if (result < 0) {
+ perror("Client sctp_sendmsg");
+ close(peeloff_sk);
+ return 1;
+ }
+
+ result = sctp_recvmsg(peeloff_sk, label, sizeof(label),
+ NULL, 0, NULL, NULL);
+ if (result < 0) {
+ perror("Client sctp_recvmsg");
+ close(peeloff_sk);
+ return 1;
+ }
+
+ close(peeloff_sk);
+ return 0;
+}
diff --git a/kernel/sctp_peer_label_bug/sctp_common.h b/kernel/sctp_peer_label_bug/sctp_common.h
new file mode 100644
index 0000000..aeec4d2
--- /dev/null
+++ b/kernel/sctp_peer_label_bug/sctp_common.h
@@ -0,0 +1,38 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* For poll(2) POLLRDHUP - Detect client close(2) */
+#endif
+
+#include <arpa/inet.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/sctp.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <errno.h>
+
+enum event_ret {
+ EVENT_OK,
+ EVENT_ADDR_MATCH,
+ EVENT_SHUTDOWN,
+ EVENT_NO_AUTH
+};
+
+void print_addr_info(struct sockaddr *sin, char *text);
+int set_subscr_events(int fd, int data_io, int assoc, int addr, int shutd);
+int handle_event(void *buf, char *cmp_addr, sctp_assoc_t *assoc_id,
+ bool verbose, char *text);
+
+static const int on = 1, off = 0;
+
+int receive_assoc(int sock, struct sockaddr_storage *sin, socklen_t *sinlen,
+ int verbose);
+int open_assoc(int sock, struct sockaddr *sin, socklen_t sinlen, int verbose);
^ permalink raw reply related [flat|nested] 2+ messages in thread
* [tests/selinux] pr775-checkpolicy-revdeps: Add a test for two-way SCTP association setup
@ 2026-09-11 13:21 Ondrej Mosnacek
0 siblings, 0 replies; 2+ messages in thread
From: Ondrej Mosnacek @ 2026-09-11 13:21 UTC (permalink / raw)
To: git-commits
A new commit has been pushed.
Repo : tests/selinux
Branch : pr775-checkpolicy-revdeps
Commit : 5e03405ed4f761adcf79e479ebf33c0dc76e4ad5
Author : Ondrej Mosnacek <omosnace@redhat.com>
Date : 2024-08-29T10:06:21+00:00
Stats : +312/-0 in 3 file(s)
URL : https://src.fedoraproject.org/tests/selinux/c/5e03405ed4f761adcf79e479ebf33c0dc76e4ad5?branch=pr775-checkpolicy-revdeps
Log:
Add a test for two-way SCTP association setup
Signed-off-by: Ondrej Mosnacek <omosnace@redhat.com>
---
diff --git a/kernel/sctp_peeloff_corner_case/main.fmf b/kernel/sctp_peeloff_corner_case/main.fmf
new file mode 100644
index 0000000..582248e
--- /dev/null
+++ b/kernel/sctp_peeloff_corner_case/main.fmf
@@ -0,0 +1,25 @@
+summary: Verify that two-way SCTP association setup doesn't trigger AVCs
+description: |
+ When two SCTP endpoints try to initiate an association
+ simultaneously with each other, they may hit various corner cases.
+ This test verifies that this scenario works correctly and that the
+ resulting association is properly labeled (by peeling it off, which
+ transfers the label onto the new socket).
+contact: Ondrej Mosnacek <omosnace@redhat.com>
+component:
+ - kernel
+framework: beakerlib
+require:
+ - make
+ - gcc
+ - lksctp-tools-devel
+ - audit
+duration: 10m
+tier: 2
+enabled: true
+adjust:
+ - enabled: false
+ when: distro < rhel-8
+ because: RHEL-7 and below don't have SCTP SELinux support
+link:
+ - verifies: https://issues.redhat.com/browse/RHEL-48647
diff --git a/kernel/sctp_peeloff_corner_case/reproducer.c b/kernel/sctp_peeloff_corner_case/reproducer.c
new file mode 100644
index 0000000..de6cedb
--- /dev/null
+++ b/kernel/sctp_peeloff_corner_case/reproducer.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPLv2
+/*
+ * Copyright (c) 2024 Red Hat, Inc.
+ * Author: Ondrej Mosnacek <omosnace@redhat.com>
+ */
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/sctp.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <netdb.h>
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+static const char * const TEST_PORTS[] = { "9998", "9999" };
+
+#define ITERATIONS 1000
+
+#define member_size(type, member) sizeof(((type *)0)->member)
+#define sizeof_up_to(type, member) (offsetof(type, member) + member_size(type, member))
+
+enum {
+ PIPE_R = 0,
+ PIPE_W,
+};
+
+int main(int argc, char **argv)
+{
+ int sock, psock, result, flags, index, i, pipefd[2], pipes[2][2];
+ struct addrinfo hints, *info;
+ struct sctp_event_subscribe subscr_events;
+ pid_t fork_pid;
+ char byte = 0x41, data[1024];
+ sctp_assoc_t assoc_id;
+ union sctp_notification *notif;
+
+ result = pipe(pipefd);
+ if (result < 0) {
+ perror("pipe");
+ return 1;
+ }
+ pipes[0][PIPE_R] = pipefd[0];
+ pipes[1][PIPE_W] = pipefd[1];
+
+ result = pipe(pipefd);
+ if (result < 0) {
+ perror("pipe");
+ return 1;
+ }
+ pipes[1][PIPE_R] = pipefd[0];
+ pipes[0][PIPE_W] = pipefd[1];
+
+ fork_pid = fork();
+ if (fork_pid < 0) {
+ perror("fork");
+ return 1;
+ }
+
+ index = !!fork_pid;
+ close(pipes[!index][PIPE_R]);
+ close(pipes[!index][PIPE_W]);
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_protocol = IPPROTO_SCTP;
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_SEQPACKET;
+
+ result = getaddrinfo(NULL, TEST_PORTS[index], &hints, &info);
+ if (result < 0) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result));
+ return 1;
+ }
+
+ sock = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
+ if (sock < 0) {
+ perror("socket");
+ return 1;
+ }
+
+ result = bind(sock, info->ai_addr, info->ai_addrlen);
+ if (result < 0) {
+ perror("bind");
+ return 1;
+ }
+
+ if (listen(sock, SOMAXCONN)) {
+ perror("listen");
+ return 1;
+ }
+
+ memset(&subscr_events, 0, sizeof(subscr_events));
+ subscr_events.sctp_association_event = 1;
+ result = setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS, &subscr_events,
+ sizeof_up_to(struct sctp_event_subscribe,
+ sctp_association_event));
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_protocol = IPPROTO_SCTP;
+ hints.ai_socktype = SOCK_SEQPACKET;
+
+ result = getaddrinfo("127.0.0.1", TEST_PORTS[!index], &hints, &info);
+ if (result < 0) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result));
+ return 1;
+ }
+
+ for (i = 0; i < ITERATIONS; i++) {
+ printf("[%i] ITERATION %i...\n", index, i);
+
+ /* synchronize */
+ result = write(pipes[index][PIPE_W], &byte, 1);
+ if (result < 0) {
+ perror("pipe");
+ return 1;
+ }
+ result = read(pipes[index][PIPE_R], &byte, 1);
+ if (result < 0) {
+ perror("pipe");
+ return 1;
+ }
+
+ result = connect(sock, info->ai_addr, info->ai_addrlen);
+ if (result < 0 && errno != EISCONN) {
+ perror("connect");
+ return 1;
+ }
+
+ /* Get assoc_id for sctp_peeloff() */
+ flags = 0;
+ result = sctp_recvmsg(sock, data, sizeof(data),
+ NULL, 0, NULL, &flags);
+ if (result < 0) {
+ perror("sctp_recvmsg");
+ return 1;
+ }
+
+ if ((flags & (MSG_NOTIFICATION | MSG_EOR)) != (MSG_NOTIFICATION | MSG_EOR)) {
+ fprintf(stderr, "[%i] Invalid sctp_recvmsg response FLAGS: %x\n", index, flags);
+ return 1;
+ }
+
+ notif = (union sctp_notification *)data;
+ if (notif->sn_header.sn_type != SCTP_ASSOC_CHANGE) {
+ fprintf(stderr, "[%i] Invalid sctp_recvmsg response type: %x\n", index, notif->sn_header.sn_type);
+ return 1;
+ }
+ assoc_id = notif->sn_assoc_change.sac_assoc_id;
+ psock = sctp_peeloff(sock, assoc_id);
+ if (psock < 0) {
+ perror("sctp_peeloff");
+ return 1;
+ }
+
+ data[0] = 42;
+ result = sctp_sendmsg(psock, data, 1, NULL, 0,
+ 0, 0, 0, 0, 0);
+ if (result < 0) {
+ perror("sctp_sendmsg");
+ return 1;
+ }
+
+ result = sctp_recvmsg(psock, data, sizeof(data),
+ NULL, 0, NULL, NULL);
+ if (result < 0) {
+ perror("sctp_recvmsg");
+ return 1;
+ }
+
+ if (data[0] != 42) {
+ fprintf(stderr, "[%i] Invalid sctp_recvmsg response: %d\n", index, (int)data[0]);
+ return 1;
+ }
+
+ close(psock);
+ printf("[%i] ITERATION %i SUCCESS!\n", index, i);
+ }
+ close(sock);
+ return 0;
+}
diff --git a/kernel/sctp_peeloff_corner_case/runtest.sh b/kernel/sctp_peeloff_corner_case/runtest.sh
new file mode 100755
index 0000000..19649bd
--- /dev/null
+++ b/kernel/sctp_peeloff_corner_case/runtest.sh
@@ -0,0 +1,100 @@
+#!/bin/bash
+# vim: dict=/usr/share/beakerlib/dictionary.vim cpt=.,w,b,u,t,i,k
+# SPDX-License-Identifier: GPLv2
+# Copyright (c) 2024 Red Hat, Inc.
+# Author: Ondrej Mosnacek <omosnace@redhat.com>
+
+# Include Beakerlib environment
+. /usr/share/beakerlib/beakerlib.sh || exit 1
+
+function installDepsYum() {
+ local yum="$1"; shift
+
+ if "$yum" install --help | grep -q -- --skip-unavailable; then
+ "$yum" install -y --skip-unavailable $*
+ elif "$yum" install --help | grep -q -- --skip-broken; then
+ "$yum" install -y --skip-broken $*
+ else
+ for req in $*; do
+ if ! rpm -q --quiet --whatprovides "$req"; then
+ "$yum" install -y "$req" || true
+ fi
+ done
+ fi
+}
+
+function installDeps() {
+ if [ -e /run/ostree-booted ]; then
+ for item in "$@"; do
+ if ! rpm -q --quiet --whatprovides "$item"; then
+ rpm-ostree --apply-live -y install "$item"
+ fi
+ done
+ elif type yum >/dev/null; then
+ installDepsYum yum "$@"
+ elif type dnf >/dev/null; then
+ installDepsYum dnf "$@"
+ fi
+}
+
+function check_avc_begin() {
+ sleep 1.1
+ date +'%x %T'
+ sleep 1.1
+}
+
+function check_avc_end() {
+ marker="marker-$RANDOM"
+
+ auditctl -m "$marker"
+
+ for (( i = 0; i < 100; i++ )); do
+ if ausearch -i -m user -ts $1 2>/dev/null </dev/null | \
+ grep -q "$marker"
+ then
+ echo $i
+ break
+ fi
+ sleep 0.1
+ done
+
+ ausearch -i -m avc -ts $1 </dev/null
+}
+
+rlJournalStart
+ rlPhaseStartSetup
+ rlRun "uname -r" 0 "Print kernel version"
+ # Determine the base kernel package name and version corresponding
+ # to the currently running kernel. Use this information to derive
+ # the correct kernel subpackages to install.
+ if KERNEL_CORE_NVRA="$(rpm -qf "/lib/modules/$(uname -r)/config")"; then
+ KERNEL_CORE_NV="${KERNEL_CORE_NVRA%-*}"
+ KERNEL_CORE_N="${KERNEL_CORE_NV%-*}"
+
+ KERNEL_PKG_VRA="${KERNEL_CORE_NVRA#$KERNEL_CORE_N-}"
+ KERNEL_PKG_BASE="${KERNEL_CORE_N%-core}"
+
+ rlLog "Detected kernel package base name '$KERNEL_PKG_BASE' and version-release.arch '$KERNEL_PKG_VRA'"
+ rlRun "installDeps $KERNEL_PKG_BASE-modules-extra-$KERNEL_PKG_VRA" 0 "Install extra kernel modules"
+ else
+ rlLog "Detected non-RPM running kernel - no extra kernel packages will be installed"
+ fi
+
+ rlRun "gcc -O2 -Wall -o reproducer reproducer.c -lsctp" 0 \
+ "Compile the reproducer"
+ rlRun "modprobe sctp" 0 "Ensure SCTP module is loaded"
+ rlPhaseEnd
+
+ rlPhaseStartTest
+ rlRun "audit_ts=\"\$(check_avc_begin)\""
+
+ rlRun "./reproducer" 0 "Run the reproducer"
+
+ rlRun "check_avc_end \"\$audit_ts\"" 1 "Check if there are AVC denials"
+ rlPhaseEnd
+
+ rlPhaseStartCleanup
+ rlRun "rm -f reproducer"
+ rlPhaseEnd
+rlJournalPrintText
+rlJournalEnd
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-09-11 13:21 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-11 13:19 [tests/selinux] pr775-checkpolicy-revdeps: Add a test for two-way SCTP association setup Ondrej Mosnacek
2026-09-11 13:21 Ondrej Mosnacek
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox