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: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
* [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

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:21 [tests/selinux] pr775-checkpolicy-revdeps: Add a test for two-way SCTP association setup Ondrej Mosnacek
  -- strict thread matches above, loose matches on Subject: below --
2026-09-11 13:19 Ondrej Mosnacek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox