root/tools/regression/security/access/testaccess.c
/*-
 * Copyright (c) 2001 Networks Associates Technology, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Written at NAI Labs at Network Associates by Robert Watson for the
 * TrustedBSD Project.
 *
 * Work sponsored by Defense Advanced Research Projects Agency under the
 * CHATS research program, CBOSS project.
 */

#include <sys/types.h>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/*
 * Regression test to check some basic cases and see if access() and
 * eaccess() are using the correct portions of the process credential.
 * This test relies on running with privilege, and on UFS filesystem
 * semantics.  Running the test in other environments may result
 * in incorrect failure identification.
 *
 * Note that this may also break if filesystem access control is
 * broken, or if the ability to check and set credentials is broken.
 *
 * Note that this test uses two hard-coded non-root UIDs; on multi-user
 * systems, these UIDs may be in use by an untrusted user, in which
 * case those users could interfere with the test.
 */

#define ROOT_UID        (uid_t)0
#define WHEEL_GID       (gid_t)0
#define TEST_UID_ONE    (uid_t)500
#define TEST_GID_ONE    (gid_t)500
#define TEST_UID_TWO    (uid_t)501
#define TEST_GID_TWO    (gid_t)501

struct file_description {
        char    *fd_name;
        uid_t    fd_owner;
        gid_t    fd_group;
        mode_t   fd_mode;
};

static struct file_description fd_list[] = {
{"test1", ROOT_UID, WHEEL_GID, 0400},
{"test2", TEST_UID_ONE, WHEEL_GID,0400},
{"test3", TEST_UID_TWO, WHEEL_GID, 0400},
{"test4", ROOT_UID, WHEEL_GID, 0040},
{"test5", ROOT_UID, TEST_GID_ONE, 0040},
{"test6", ROOT_UID, TEST_GID_TWO, 0040}};

static int fd_list_count = sizeof(fd_list) /
    sizeof(struct file_description);

int
setup(void)
{
        int i, error;

        for (i = 0; i < fd_list_count; i++) {
                error = open(fd_list[i].fd_name, O_CREAT | O_EXCL, fd_list[i].fd_mode);
                if (error == -1) {
                        perror("open");
                        return (error);
                }
                close(error);
                error = chown(fd_list[i].fd_name, fd_list[i].fd_owner,
                    fd_list[i].fd_group);
                if (error) {
                        perror("chown");
                        return (error);
                }
        }
        return (0);
}

int
restoreprivilege(void)
{
        int error;

        error = setreuid(ROOT_UID, ROOT_UID);
        if (error)
                return (error);

        error = setregid(WHEEL_GID, WHEEL_GID);
        if (error)
                return (error);

        return (0);
}

int
reportprivilege(char *message)
{
        uid_t euid, ruid, suid;
        gid_t egid, rgid, sgid;
        int error;

        error = getresuid(&ruid, &euid, &suid);
        if (error) {
                perror("getresuid");
                return (error);
        }

        error = getresgid(&rgid, &egid, &sgid);
        if (error) {
                perror("getresgid");
                return (error);
        }

        if (message)
                printf("%s: ", message);
        printf("ruid: %d, euid: %d, suid: %d,     ", ruid, euid, suid);
        printf("rgid: %d, egid: %d, sgid: %d\n", rgid, egid, sgid);

        return (0);
}

int
cleanup(void)
{
        int i, error;

        error = restoreprivilege();
        if (error) {
                perror("restoreprivilege");
                return (error);
        }

        for (i = 0; i < fd_list_count; i++) {
                error = unlink(fd_list[i].fd_name);
                if (error)
                        return (error);
        }

        return (0);
}

int
main(int argc, char *argv[])
{
        int error, errorseen;

        if (geteuid() != 0) {
                fprintf(stderr, "testaccess must run as root.\n");
                exit (EXIT_FAILURE);
        }

        error = setup();
        if (error) {
                cleanup();
                exit (EXIT_FAILURE);
        }

        /* Make sure saved uid is set appropriately. */
        error = setresuid(ROOT_UID, ROOT_UID, ROOT_UID);
        if (error) {
                perror("setresuid");
                cleanup();
        }

        /* Clear out additional groups. */
        error = setgroups(0, NULL);
        if (error) {
                perror("setgroups");
                cleanup();
        }

        /* Make sure saved gid is set appropriately. */
        error = setresgid(WHEEL_GID, WHEEL_GID, WHEEL_GID);
        if (error) {
                perror("setresgid");
                cleanup();
        }

        /*
         * UID-only tests.
         */

        /* Check that saved uid is not used */
        error = setresuid(TEST_UID_ONE, TEST_UID_ONE, ROOT_UID);
        if (error) {
                perror("setresuid.1");
                cleanup();
                exit (EXIT_FAILURE);
        }

        errorseen = 0;

        error = access("test1", R_OK);
        if (!error) {
                fprintf(stderr, "saved uid used instead of real uid\n");
                errorseen++;
        }

#ifdef EACCESS_AVAILABLE
        error = eaccess("test1", R_OK);
        if (!error) {
                fprintf(stderr, "saved uid used instead of effective uid\n");
                errorseen++;
        }
#endif

        error = restoreprivilege();
        if (error) {
                perror("restoreprivilege");
                cleanup();
                exit (EXIT_FAILURE);
        }

        error = setresuid(TEST_UID_ONE, TEST_UID_TWO, ROOT_UID);
        if (error) {
                perror("setresid.2");
                cleanup();
                exit (EXIT_FAILURE);
        }

        /* Check that the real uid is used, not the effective uid */
        error = access("test2", R_OK);
        if (error) {
                fprintf(stderr, "Effective uid was used instead of real uid in access().\n");
                errorseen++;
        }

#ifdef EACCESS_AVAILABLE
        /* Check that the effective uid is used, not the real uid */
        error = eaccess("test3", R_OK);
        if (error) {
                fprintf(stderr, "Real uid was used instead of effective uid in eaccess().\n");
                errorseen++;
        }
#endif

        /* Check that the real uid is used, not the effective uid */
        error = access("test3", R_OK);
        if (!error) {
                fprintf(stderr, "Effective uid was used instead of real uid in access().\n");
                errorseen++;
        }

#ifdef EACCESS_AVAILABLE
        /* Check that the effective uid is used, not the real uid */
        error = eaccess("test2", R_OK);
        if (!error) {
                fprintf(stderr, "Real uid was used instead of effective uid in eaccess().\n");
                errorseen++;
        }
#endif

        error = restoreprivilege();
        if (error) {
                perror("restoreprivilege");
                cleanup();
                exit (EXIT_FAILURE);
        }

        error = setresgid(TEST_GID_ONE, TEST_GID_TWO, WHEEL_GID);
        if (error) {
                perror("setresgid.1");
                cleanup();
                exit (EXIT_FAILURE);
        }

        /* Set non-root effective uid to avoid excess privilege. */
        error = setresuid(TEST_UID_ONE, TEST_UID_ONE, ROOT_UID);
        if (error) {
                perror("setresuid.3");
                cleanup();
                exit (EXIT_FAILURE);
        }

        /* Check that the saved gid is not used */
        error = access("test4", R_OK);
        if (!error) {
                fprintf(stderr, "saved gid used instead of real gid\n");
        }

#ifdef EACCESS_AVAILABLE
        error = eaccess("test4", R_OK);
        if (!error) {
                fprintf(stderr, "saved gid used instead of effective gid\n");
                errorseen++;
        }
#endif

        /* Check that the real gid is used, not the effective gid */
        error = access("test5", R_OK);
        if (error) {
                fprintf(stderr, "Effective gid was used instead of real gid in access().\n");
                errorseen++;
        }

#ifdef EACCESS_AVAILABLE
        /* Check that the effective gid is used, not the real gid */
        error = eaccess("test6", R_OK);
        if (error) {
                fprintf(stderr, "Real gid was used instead of effective gid in eaccess().\n");
                errorseen++;
        }
#endif

        /* Check that the real gid is used, not the effective gid */
        error = access("test6", R_OK);
        if (!error) {
                fprintf(stderr, "Effective gid was used instead of real gid in access().\n");
                errorseen++;
        }

#ifdef EACCESS_AVAILABLE
        /* Check that the effective gid is used, not the real gid */
        error = eaccess("test5", R_OK);
        if (!error) {
                fprintf(stderr, "Real gid was used instead of effective gid in eaccess().\n");
                errorseen++;
        }
#endif

        fprintf(stderr, "%d errors seen.\n", errorseen);

        /*
         * All tests done, restore and clean up
         */

        error = cleanup();
        if (error) {
                perror("cleanup");
                exit (EXIT_FAILURE);
        }

        exit (EXIT_SUCCESS);
}