root/tools/testing/selftests/namespaces/listns_pagination_bug.c
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <errno.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include "../kselftest_harness.h"
#include "../filesystems/utils.h"
#include "wrappers.h"

/*
 * Minimal test case to reproduce KASAN out-of-bounds in listns pagination.
 *
 * The bug occurs when:
 * 1. Filtering by a specific namespace type (e.g., CLONE_NEWUSER)
 * 2. Using pagination (req.ns_id != 0)
 * 3. The lookup_ns_id_at() call in do_listns() passes ns_type=0 instead of
 *    the filtered type, causing it to search the unified tree and potentially
 *    return a namespace of the wrong type.
 */
TEST(pagination_with_type_filter)
{
        struct ns_id_req req = {
                .size = sizeof(req),
                .spare = 0,
                .ns_id = 0,
                .ns_type = CLONE_NEWUSER,  /* Filter by user namespace */
                .spare2 = 0,
                .user_ns_id = 0,
        };
        pid_t pids[10];
        int num_children = 10;
        int i;
        int sv[2];
        __u64 first_batch[3];
        ssize_t ret;

        ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);

        /* Create children with user namespaces */
        for (i = 0; i < num_children; i++) {
                pids[i] = fork();
                ASSERT_GE(pids[i], 0);

                if (pids[i] == 0) {
                        char c;
                        close(sv[0]);

                        if (setup_userns() < 0) {
                                close(sv[1]);
                                exit(1);
                        }

                        /* Signal parent we're ready */
                        if (write(sv[1], &c, 1) != 1) {
                                close(sv[1]);
                                exit(1);
                        }

                        /* Wait for parent signal to exit */
                        if (read(sv[1], &c, 1) != 1) {
                                close(sv[1]);
                                exit(1);
                        }

                        close(sv[1]);
                        exit(0);
                }
        }

        close(sv[1]);

        /* Wait for all children to signal ready */
        for (i = 0; i < num_children; i++) {
                char c;
                if (read(sv[0], &c, 1) != 1) {
                        close(sv[0]);
                        for (int j = 0; j < num_children; j++)
                                kill(pids[j], SIGKILL);
                        for (int j = 0; j < num_children; j++)
                                waitpid(pids[j], NULL, 0);
                        ASSERT_TRUE(false);
                }
        }

        /* First batch - this should work */
        ret = sys_listns(&req, first_batch, 3, 0);
        if (ret < 0) {
                if (errno == ENOSYS) {
                        close(sv[0]);
                        for (i = 0; i < num_children; i++)
                                kill(pids[i], SIGKILL);
                        for (i = 0; i < num_children; i++)
                                waitpid(pids[i], NULL, 0);
                        SKIP(return, "listns() not supported");
                }
                ASSERT_GE(ret, 0);
        }

        TH_LOG("First batch returned %zd entries", ret);

        if (ret == 3) {
                __u64 second_batch[3];

                /* Second batch - pagination triggers the bug */
                req.ns_id = first_batch[2];  /* Continue from last ID */
                ret = sys_listns(&req, second_batch, 3, 0);

                TH_LOG("Second batch returned %zd entries", ret);
                ASSERT_GE(ret, 0);
        }

        /* Signal all children to exit */
        for (i = 0; i < num_children; i++) {
                char c = 'X';
                if (write(sv[0], &c, 1) != 1) {
                        close(sv[0]);
                        for (int j = i; j < num_children; j++)
                                kill(pids[j], SIGKILL);
                        for (int j = 0; j < num_children; j++)
                                waitpid(pids[j], NULL, 0);
                        ASSERT_TRUE(false);
                }
        }

        close(sv[0]);

        /* Cleanup */
        for (i = 0; i < num_children; i++) {
                int status;
                waitpid(pids[i], &status, 0);
        }
}

TEST_HARNESS_MAIN