root/tools/testing/selftests/powerpc/dexcr/dexcr_test.c
// SPDX-License-Identifier: GPL-2.0-or-later

#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <unistd.h>

#include "dexcr.h"
#include "utils.h"

/*
 * Helper function for testing the behaviour of a newly exec-ed process
 */
static int dexcr_prctl_onexec_test_child(unsigned long which, const char *status)
{
        unsigned long dexcr = mfspr(SPRN_DEXCR_RO);
        unsigned long aspect = pr_which_to_aspect(which);
        int ctrl = pr_get_dexcr(which);

        if (!strcmp(status, "set")) {
                FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET),
                                 "setting aspect across exec not applied");

                FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC),
                                 "setting aspect across exec not inherited");

                FAIL_IF_EXIT_MSG(!(aspect & dexcr), "setting aspect across exec did not take effect");
        } else if (!strcmp(status, "clear")) {
                FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR),
                                 "clearing aspect across exec not applied");

                FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC),
                                 "clearing aspect across exec not inherited");

                FAIL_IF_EXIT_MSG(aspect & dexcr, "clearing aspect across exec did not take effect");
        } else {
                FAIL_IF_EXIT_MSG(true, "unknown expected status");
        }

        return 0;
}

/*
 * Test that the given prctl value can be manipulated freely
 */
static int dexcr_prctl_aspect_test(unsigned long which)
{
        unsigned long aspect = pr_which_to_aspect(which);
        pid_t pid;
        int ctrl;
        int err;
        int errno_save;

        SKIP_IF_MSG(!dexcr_exists(), "DEXCR not supported");
        SKIP_IF_MSG(!pr_dexcr_aspect_supported(which), "DEXCR aspect not supported");
        SKIP_IF_MSG(!pr_dexcr_aspect_editable(which), "DEXCR aspect not editable with prctl");

        /* We reject invalid combinations of arguments */
        err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR);
        errno_save = errno;
        FAIL_IF_MSG(err != -1, "simultaneous set and clear should be rejected");
        FAIL_IF_MSG(errno_save != EINVAL, "simultaneous set and clear should be rejected with EINVAL");

        err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET_ONEXEC | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC);
        errno_save = errno;
        FAIL_IF_MSG(err != -1, "simultaneous set and clear on exec should be rejected");
        FAIL_IF_MSG(errno_save != EINVAL, "simultaneous set and clear on exec should be rejected with EINVAL");

        /* We set the aspect */
        err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET);
        FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_SET failed");

        ctrl = pr_get_dexcr(which);
        FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET), "config value not PR_PPC_DEXCR_CTRL_SET");
        FAIL_IF_MSG(ctrl & PR_PPC_DEXCR_CTRL_CLEAR, "config value unexpected clear flag");
        FAIL_IF_MSG(!(aspect & mfspr(SPRN_DEXCR_RO)), "setting aspect did not take effect");

        /* We clear the aspect */
        err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_CLEAR);
        FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_CLEAR failed");

        ctrl = pr_get_dexcr(which);
        FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR), "config value not PR_PPC_DEXCR_CTRL_CLEAR");
        FAIL_IF_MSG(ctrl & PR_PPC_DEXCR_CTRL_SET, "config value unexpected set flag");
        FAIL_IF_MSG(aspect & mfspr(SPRN_DEXCR_RO), "clearing aspect did not take effect");

        /* We make it set on exec (doesn't change our current value) */
        err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET_ONEXEC);
        FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_SET_ONEXEC failed");

        ctrl = pr_get_dexcr(which);
        FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR), "process aspect should still be cleared");
        FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC), "config value not PR_PPC_DEXCR_CTRL_SET_ONEXEC");
        FAIL_IF_MSG(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC, "config value unexpected clear on exec flag");
        FAIL_IF_MSG(aspect & mfspr(SPRN_DEXCR_RO), "scheduling aspect to set on exec should not change it now");

        /* We make it clear on exec (doesn't change our current value) */
        err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC);
        FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC failed");

        ctrl = pr_get_dexcr(which);
        FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR), "process aspect config should still be cleared");
        FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC), "config value not PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC");
        FAIL_IF_MSG(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC, "config value unexpected set on exec flag");
        FAIL_IF_MSG(aspect & mfspr(SPRN_DEXCR_RO), "process aspect should still be cleared");

        /* We allow setting the current and on-exec value in a single call */
        err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC);
        FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC failed");

        ctrl = pr_get_dexcr(which);
        FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET), "config value not PR_PPC_DEXCR_CTRL_SET");
        FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC), "config value not PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC");
        FAIL_IF_MSG(!(aspect & mfspr(SPRN_DEXCR_RO)), "process aspect should be set");

        err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_CLEAR | PR_PPC_DEXCR_CTRL_SET_ONEXEC);
        FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_CLEAR | PR_PPC_DEXCR_CTRL_SET_ONEXEC failed");

        ctrl = pr_get_dexcr(which);
        FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR), "config value not PR_PPC_DEXCR_CTRL_CLEAR");
        FAIL_IF_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC), "config value not PR_PPC_DEXCR_CTRL_SET_ONEXEC");
        FAIL_IF_MSG(aspect & mfspr(SPRN_DEXCR_RO), "process aspect should be clear");

        /* Verify the onexec value is applied across exec */
        pid = fork();
        if (!pid) {
                char which_str[32] = {};
                char *args[] = { "dexcr_prctl_onexec_test_child", which_str, "set", NULL };
                unsigned int ctrl = pr_get_dexcr(which);

                sprintf(which_str, "%lu", which);

                FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_SET_ONEXEC),
                                 "setting aspect on exec not copied across fork");

                FAIL_IF_EXIT_MSG(mfspr(SPRN_DEXCR_RO) & aspect,
                                 "setting aspect on exec wrongly applied to fork");

                execve("/proc/self/exe", args, NULL);
                _exit(errno);
        }
        await_child_success(pid);

        err = pr_set_dexcr(which, PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC);
        FAIL_IF_MSG(err, "PR_PPC_DEXCR_CTRL_SET | PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC failed");

        pid = fork();
        if (!pid) {
                char which_str[32] = {};
                char *args[] = { "dexcr_prctl_onexec_test_child", which_str, "clear", NULL };
                unsigned int ctrl = pr_get_dexcr(which);

                sprintf(which_str, "%lu", which);

                FAIL_IF_EXIT_MSG(!(ctrl & PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC),
                                 "clearing aspect on exec not copied across fork");

                FAIL_IF_EXIT_MSG(!(mfspr(SPRN_DEXCR_RO) & aspect),
                                 "clearing aspect on exec wrongly applied to fork");

                execve("/proc/self/exe", args, NULL);
                _exit(errno);
        }
        await_child_success(pid);

        return 0;
}

static int dexcr_prctl_ibrtpd_test(void)
{
        return dexcr_prctl_aspect_test(PR_PPC_DEXCR_IBRTPD);
}

static int dexcr_prctl_srapd_test(void)
{
        return dexcr_prctl_aspect_test(PR_PPC_DEXCR_SRAPD);
}

static int dexcr_prctl_nphie_test(void)
{
        return dexcr_prctl_aspect_test(PR_PPC_DEXCR_NPHIE);
}

int main(int argc, char *argv[])
{
        int err = 0;

        /*
         * Some tests require checking what happens across exec, so we may be
         * invoked as the child of a particular test
         */
        if (argc > 1) {
                if (argc == 3 && !strcmp(argv[0], "dexcr_prctl_onexec_test_child")) {
                        unsigned long which;

                        err = parse_ulong(argv[1], strlen(argv[1]), &which, 10);
                        FAIL_IF_MSG(err, "failed to parse which value for child");

                        return dexcr_prctl_onexec_test_child(which, argv[2]);
                }

                FAIL_IF_MSG(true, "unknown test case");
        }

        /*
         * Otherwise we are the main test invocation and run the full suite
         */
        err |= test_harness(dexcr_prctl_ibrtpd_test, "dexcr_prctl_ibrtpd");
        err |= test_harness(dexcr_prctl_srapd_test, "dexcr_prctl_srapd");
        err |= test_harness(dexcr_prctl_nphie_test, "dexcr_prctl_nphie");

        return err;
}