diff --git a/kernel/tkernel/netbind.c b/kernel/tkernel/netbind.c index ba1878eb4e4c0ac65d1a9f041df7ca99208eb7bc..1edccb928280c02a653062d8078d021588ea31b9 100644 --- a/kernel/tkernel/netbind.c +++ b/kernel/tkernel/netbind.c @@ -20,7 +20,7 @@ static int netbind_proc_show(struct seq_file *m, void *v) { int i; for (i = 1; i < PROT_SOCK; i++) { - if (nonpriv_prot_sock_flag[i]) + if (READ_ONCE(nonpriv_prot_sock_flag[i])) seq_printf(m, "%d\n", i); } return 0; @@ -75,7 +75,7 @@ static ssize_t netbind_proc_write(struct file *file, const char __user *buf, if (!port || port >= PROT_SOCK) goto out; - nonpriv_prot_sock_flag[port] = en; + WRITE_ONCE(nonpriv_prot_sock_flag[port], en); err = length; out: diff --git a/tools/testing/selftests/tkernel/netbind.sh b/tools/testing/selftests/tkernel/netbind.sh index 7ef54ea5e6f18910201df14ef18289cb4887e58f..0799f146ab020c15dd42b17309299e8fd436dbab 100755 --- a/tools/testing/selftests/tkernel/netbind.sh +++ b/tools/testing/selftests/tkernel/netbind.sh @@ -7,7 +7,7 @@ PORT=83 OUT_OF_RANGE_PORT=1500 DEFAULT_PORT_START=1024 RAISED_PORT_START=2048 -TESTS=12 +TESTS=16 test_no=0 failures=0 @@ -31,6 +31,22 @@ result() fi } +result_or_skip() +{ + rc=$1 + expected=$2 + description=$3 + + if [ "$rc" -eq "$KSFT_SKIP" ]; then + test_no=$((test_no + 1)) + echo "ok $test_no - $description # SKIP protocol unavailable" + return + fi + + [ "$rc" -eq "$expected" ] + result $? "$description" +} + port_is_listed() { grep -qx "$PORT" "$PROC_FILE" @@ -46,10 +62,50 @@ run_helper() family=${1:-4} port=${2:-$PORT} port_start=${3:-$DEFAULT_PORT_START} - "$helper" "$family" "$port" "$port_start" + protocol=${4:-tcp} + "$helper" "$family" "$port" "$port_start" "$protocol" return $? } +stress_state_access() +{ + ( + i=0 + while [ "$i" -lt 200 ]; do + printf '+%s\n' "$PORT" > "$PROC_FILE" || exit 1 + printf -- '-%s\n' "$PORT" > "$PROC_FILE" || exit 1 + i=$((i + 1)) + done + ) & + writer=$! + + workers= + for worker in 1 2 3 4; do + ( + i=0 + while [ "$i" -lt 25 ]; do + run_helper 4 >/dev/null 2>&1 + rc=$? + [ "$rc" -eq 0 ] || [ "$rc" -eq 1 ] || exit 1 + i=$((i + 1)) + done + ) & + workers="$workers $!" + done + + failed=0 + while kill -0 "$writer" 2>/dev/null; do + cat "$PROC_FILE" >/dev/null || failed=1 + done + wait "$writer" || failed=1 + for worker in $workers; do + wait "$worker" || failed=1 + done + + remove_port + return "$failed" +} + invalid_write_is_rejected() { before=$(cat "$PROC_FILE") @@ -114,12 +170,24 @@ result $? "unprivileged bind succeeds for an allowlisted port" run_helper 6 result $? "IPv6 bind succeeds for an allowlisted port" +run_helper 4 "$PORT" "$DEFAULT_PORT_START" sctp +result_or_skip $? 1 "SCTP/IPv4 bind remains denied with a TCP allowlist" + +run_helper 6 "$PORT" "$DEFAULT_PORT_START" sctp +result_or_skip $? 1 "SCTP/IPv6 bind remains denied with a TCP allowlist" + +stress_state_access +result $? "concurrent procfs access and bind checks complete" + remove_port run_helper rc=$? [ "$rc" -eq 1 ] result $? "unprivileged bind is denied after allowlist removal" +run_helper 4 "$PORT" "$DEFAULT_PORT_START" sctp +result_or_skip $? 1 "SCTP bind is denied after allowlist removal" + run_helper 4 "$OUT_OF_RANGE_PORT" "$RAISED_PORT_START" rc=$? [ "$rc" -eq 1 ] diff --git a/tools/testing/selftests/tkernel/netbind_test.c b/tools/testing/selftests/tkernel/netbind_test.c index db43aad763987363f9077fea184f53c1997f1237..5a7e0e4b8dbda19a22efd3b8ee197a23c8a1f224 100644 --- a/tools/testing/selftests/tkernel/netbind_test.c +++ b/tools/testing/selftests/tkernel/netbind_test.c @@ -57,10 +57,11 @@ int main(int argc, char **argv) socklen_t addr_len; char *end; long port, port_start; - int family, fd; + int family, protocol, fd; - if (argc != 4) { - fprintf(stderr, "usage: %s FAMILY PORT UNPRIVILEGED_PORT_START\n", + if (argc != 4 && argc != 5) { + fprintf(stderr, + "usage: %s FAMILY PORT UNPRIVILEGED_PORT_START [tcp|sctp]\n", argv[0]); return 2; } @@ -95,6 +96,16 @@ int main(int argc, char **argv) return 2; } + protocol = IPPROTO_TCP; + if (argc == 5) { + if (!strcmp(argv[4], "sctp")) + protocol = IPPROTO_SCTP; + else if (strcmp(argv[4], "tcp")) { + fprintf(stderr, "invalid protocol: %s\n", argv[4]); + return 2; + } + } + if (unshare(CLONE_NEWNET)) { perror("unshare(CLONE_NEWNET)"); return errno == EPERM ? KSFT_SKIP : 2; @@ -110,8 +121,12 @@ int main(int argc, char **argv) return KSFT_SKIP; } - fd = socket(family, SOCK_STREAM | SOCK_CLOEXEC, 0); + fd = socket(family, SOCK_STREAM | SOCK_CLOEXEC, protocol); if (fd < 0) { + if (protocol == IPPROTO_SCTP && + (errno == EPROTONOSUPPORT || errno == ESOCKTNOSUPPORT || + errno == EAFNOSUPPORT)) + return KSFT_SKIP; perror("socket"); return 2; }