Files
Fred WrightandChristopher Nielsen 384b03e085 test_faccessat: Make parallelizable.
Makes created directory name unique.

Also adds success/failure message.

TESTED:
Passes on all platforms, including running in parallel.
2025-07-11 17:34:31 -04:00

406 lines
19 KiB
C

/*
* Copyright (C) 2023 raf <raf@raf.org>
* Copyright (c) 2025 Frederick H. G. Wright II <fw@fwright.net>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <libgen.h>
#include <pwd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/stat.h>
#ifndef TEST_TEMP
#define TEST_TEMP "/dev/null"
#endif
int main(int argc, char *argv[])
{
int failures = 0;
uid_t real_uid = getuid();
uid_t effective_uid = geteuid();
uid_t real_gid = getgid();
uid_t effective_gid = getegid();
pid_t pid = getpid();
char *progname = basename(argv[0]);
char tmpdir[MAXPATHLEN];
char readable_path[MAXPATHLEN];
char writable_path[MAXPATHLEN];
char executable_path[MAXPATHLEN];
char inaccessible_path[MAXPATHLEN];
char nonexistent_path[MAXPATHLEN];
// When supplied with arguments, compare faccessat() against access().
// We can't test AT_EACCESS here because access() can't do that.
// But some tests will be setuid, some setgid, and some neither.
// See manual_tests/do_test_faccessat_setuid.sh for details.
if (argc > 1)
{
// For each file, test all modes in isolation and in all combinations
int modes[8] = { F_OK, R_OK, W_OK, X_OK, R_OK|W_OK, W_OK|X_OK, X_OK|R_OK, R_OK|W_OK|X_OK };
int a, m;
for (a = 1; a < argc; ++a)
{
for (m = 0; m < 8; ++m)
{
int faccessat_rc = faccessat(AT_FDCWD, argv[a], modes[m], 0);
int faccessat_errno = errno;
int access_rc = access(argv[a], modes[m]);
int access_errno = errno;
if (faccessat_rc != access_rc || (faccessat_rc < 0 && faccessat_errno != access_errno))
{
if (!failures)
fprintf(stderr, "faccessat_setuid: uid=%s euid=%s gid=%s egid=%s\n",
getpwuid(real_uid) ? getpwuid(real_uid)->pw_name : "?",
getpwuid(effective_uid) ? getpwuid(effective_uid)->pw_name : "?",
getgrgid(real_gid) ? getgrgid(real_gid)->gr_name : "?",
getgrgid(effective_gid) ? getgrgid(effective_gid)->gr_name : "?"
);
++failures;
fprintf(stderr, "faccessat(%s, %s%s%s%s) fail: %d %s != %d %s\n",
argv[a],
(modes[m] == F_OK) ? "F" : "",
(modes[m] & R_OK) ? "R" : "",
(modes[m] & W_OK) ? "W" : "",
(modes[m] & X_OK) ? "X" : "",
faccessat_rc,
(faccessat_rc == -1) ? strerror(faccessat_errno) : "",
access_rc,
(access_rc == -1) ? strerror(access_errno) : ""
);
char buf[1024];
snprintf(buf, 1024, "/bin/ls -l %s", argv[a]);
system(buf);
}
}
}
unlink(argv[0]);
printf("%s %s.\n", progname, failures ? "failed" : "passed");
return (failures) ? 1 : 0;
}
// Without arguments, just create readable/writable/executable/inaccessible
// files and test faccessat() with and without AT_EACCESS. This is part of
// the automatic tests, but as part of manual testing might be setuid.
// Instead of comparing against access(), these tests have explicit expected
// results.
(void) snprintf(tmpdir, sizeof(tmpdir), TEST_TEMP "/%s-%u",
progname, pid);
#define SETUP_PATH(name) \
(void) snprintf(name##_path, sizeof(name##_path), "%s/" #name, tmpdir)
SETUP_PATH(readable);
SETUP_PATH(writable);
SETUP_PATH(executable);
SETUP_PATH(inaccessible);
SETUP_PATH(nonexistent);
/* Create temporary directory if not already there (see make test_faccessat_setuid) */
int mkdir_rc = mkdir(tmpdir, 0700);
int mkdir_errno = errno;
if (mkdir_rc == -1 && mkdir_errno != EEXIST)
{
fprintf(stderr, "failed to create tmp directory %s: %s\n",
tmpdir, strerror(errno));
return 1;
}
/* Create readable/writable/executable/inaccessible files (owned by effective user) */
int readable_fd = creat(readable_path, 0400);
if (readable_fd == -1)
{
fprintf(stderr, "failed to create readable file %s: %s\n", readable_path, strerror(errno));
if (mkdir_rc == 0)
rmdir(tmpdir);
return 1;
}
close(readable_fd);
int writable_fd = creat(writable_path, 0200);
if (writable_fd == -1)
{
fprintf(stderr, "failed to create writable file %s: %s\n", writable_path, strerror(errno));
unlink(readable_path);
if (mkdir_rc == 0)
rmdir(tmpdir);
return 1;
}
close(writable_fd);
int executable_fd = creat(executable_path, 0100);
if (executable_fd == -1)
{
fprintf(stderr, "failed to create executable file %s: %s\n", executable_path, strerror(errno));
unlink(readable_path);
unlink(writable_path);
if (mkdir_rc == 0)
rmdir(tmpdir);
return 1;
}
close(executable_fd);
int inaccessible_fd = creat(inaccessible_path, 0000);
if (inaccessible_fd == -1)
{
fprintf(stderr, "failed to create inaccessible file %s: %s\n", inaccessible_path, strerror(errno));
unlink(readable_path);
unlink(writable_path);
unlink(executable_path);
if (mkdir_rc == 0)
rmdir(tmpdir);
return 1;
}
close(inaccessible_fd);
/* Test faccessat() with no flags and with AT_EACCESS */
int dfd = open(".", O_RDONLY); errno = 0;
int readable_rok = faccessat(dfd, readable_path, R_OK, 0);
int readable_rok_errno = errno; errno = 0;
int writable_wok = faccessat(dfd, writable_path, W_OK, 0);
int writable_wok_errno = errno; errno = 0;
int executable_xok = faccessat(dfd, executable_path, X_OK, 0);
int executable_xok_errno = errno; errno = 0;
int inaccessible_rok = faccessat(dfd, inaccessible_path, R_OK, 0);
int inaccessible_rok_errno = errno; errno = 0;
int inaccessible_wok = faccessat(dfd, inaccessible_path, W_OK, 0);
int inaccessible_wok_errno = errno; errno = 0;
int inaccessible_xok = faccessat(dfd, inaccessible_path, X_OK, 0);
int inaccessible_xok_errno = errno; errno = 0;
int readable_erok = faccessat(dfd, readable_path, R_OK, AT_EACCESS);
int readable_erok_errno = errno; errno = 0;
int writable_ewok = faccessat(dfd, writable_path, W_OK, AT_EACCESS);
int writable_ewok_errno = errno; errno = 0;
int executable_exok = faccessat(dfd, executable_path, X_OK, AT_EACCESS);
int executable_exok_errno = errno; errno = 0;
int inaccessible_erok = faccessat(dfd, inaccessible_path, R_OK, AT_EACCESS);
int inaccessible_erok_errno = errno; errno = 0;
int inaccessible_ewok = faccessat(dfd, inaccessible_path, W_OK, AT_EACCESS);
int inaccessible_ewok_errno = errno; errno = 0;
int inaccessible_exok = faccessat(dfd, inaccessible_path, X_OK, AT_EACCESS);
int inaccessible_exok_errno = errno; errno = 0;
int readable_fok = faccessat(dfd, readable_path, F_OK, 0);
int readable_fok_errno = errno; errno = 0;
int writable_fok = faccessat(dfd, writable_path, F_OK, 0);
int writable_fok_errno = errno; errno = 0;
int executable_fok = faccessat(dfd, executable_path, F_OK, 0);
int executable_fok_errno = errno; errno = 0;
int inaccessible_fok = faccessat(dfd, inaccessible_path, F_OK, 0);
int inaccessible_fok_errno = errno; errno = 0;
int readable_efok = faccessat(dfd, readable_path, F_OK, AT_EACCESS);
int readable_efok_errno = errno; errno = 0;
int writable_efok = faccessat(dfd, writable_path, F_OK, AT_EACCESS);
int writable_efok_errno = errno; errno = 0;
int executable_efok = faccessat(dfd, executable_path, F_OK, AT_EACCESS);
int executable_efok_errno = errno; errno = 0;
int inaccessible_efok = faccessat(dfd, inaccessible_path, F_OK, AT_EACCESS);
int inaccessible_efok_errno = errno; errno = 0;
int nonexistent_fok = faccessat(dfd, nonexistent_path, F_OK, 0);
int nonexistent_fok_errno = errno; errno = 0;
int nonexistent_efok = faccessat(dfd, nonexistent_path, F_OK, AT_EACCESS);
int nonexistent_efok_errno = errno; errno = 0;
close (dfd);
#define TEST(cond, message, e) if (!(cond)) { errno = 0; fprintf(stderr, "%s %s\n", message, (e) ? strerror(e) : ""); ++failures; }
/* Test F_OK */
TEST(readable_fok == 0, "root: faccessat(readable F_OK) does not exist = FAIL", readable_fok_errno)
TEST(writable_fok == 0, "root: faccessat(writable F_OK) does not exist = FAIL", writable_fok_errno)
TEST(executable_fok == 0, "root: faccessat(executable F_OK) does not exist = FAIL", executable_fok_errno)
TEST(inaccessible_fok == 0, "root: faccessat(inaccessible F_OK) does not exist = FAIL", inaccessible_fok_errno)
TEST(readable_efok == 0, "root: faccessat(readable F_OK AT_EACCESS) does not exist = FAIL", readable_efok_errno)
TEST(writable_efok == 0, "root: faccessat(writable F_OK AT_EACCESS) does not exist = FAIL", writable_efok_errno)
TEST(executable_efok == 0, "root: faccessat(executable F_OK AT_EACCESS) does not exist = FAIL", executable_efok_errno)
TEST(inaccessible_efok == 0, "root: faccessat(inaccessible F_OK AT_EACCESS) does not exist = FAIL", inaccessible_efok_errno)
TEST(nonexistent_fok == -1, "root: faccessat(nonexistent F_OK) does exist = FAIL", nonexistent_fok_errno)
TEST(nonexistent_efok == -1, "root: faccessat(nonexistent F_OK AT_EACCESS) does exist = FAIL", nonexistent_efok_errno)
/* Test non-setuid program */
if (real_uid == effective_uid)
{
/* Test as non-root user */
if (real_uid != 0)
{
TEST(readable_rok == 0, "not root: faccessat(readable R_OK) is unreadable = FAIL", readable_rok_errno)
TEST(writable_wok == 0, "not root: faccessat(writable W_OK) is unwritable = FAIL", writable_wok_errno)
TEST(executable_xok == 0, "not root: faccessat(executable X_OK) is unexecutable = FAIL", executable_xok_errno)
TEST(inaccessible_rok == -1, "not root: faccessat(inaccessible R_OK) is readable = FAIL", inaccessible_rok_errno)
TEST(inaccessible_wok == -1, "not root: faccessat(inaccessible W_OK) is writable = FAIL", inaccessible_wok_errno)
TEST(inaccessible_xok == -1, "not root: faccessat(inaccessible X_OK) is executable = FAIL", inaccessible_xok_errno)
/* This isn't meaningful because uid == euid */
TEST(readable_erok == 0, "not root: faccessat(readable R_OK AT_EACCESS) is unreadable = FAIL", readable_erok_errno)
TEST(writable_ewok == 0, "not root: faccessat(writable W_OK AT_EACCESS) is unwritable = FAIL", writable_ewok_errno)
TEST(executable_exok == 0, "not root: faccessat(executable X_OK AT_EACCESS) is unexecutable = FAIL", executable_exok_errno)
TEST(inaccessible_erok == -1, "not root: faccessat(inaccessible R_OK AT_EACCESS) is readable = FAIL", inaccessible_erok_errno)
TEST(inaccessible_ewok == -1, "not root: faccessat(inaccessible W_OK AT_EACCESS) is writable = FAIL", inaccessible_ewok_errno)
TEST(inaccessible_exok == -1, "not root: faccessat(inaccessible X_OK AT_EACCESS) is executable = FAIL", inaccessible_exok_errno)
}
/* Test as root user */
else
{
TEST(readable_rok == 0, "root: faccessat(readable R_OK) is unreadable = FAIL", readable_rok_errno)
TEST(writable_wok == 0, "root: faccessat(writable W_OK) is unwritable = FAIL", writable_wok_errno)
TEST(executable_xok == 0, "root: faccessat(executable X_OK) is unexecutable = FAIL", executable_xok_errno)
TEST(inaccessible_rok == 0, "root: faccessat(inaccessible R_OK) is unreadable = FAIL", inaccessible_rok_errno)
TEST(inaccessible_wok == 0, "root: faccessat(inaccessible W_OK) is unwritable = FAIL", inaccessible_wok_errno)
TEST(inaccessible_xok == -1, "root: faccessat(inaccessible X_OK) is executable = FAIL", inaccessible_xok_errno)
/* This isn't meaningful because uid == euid */
TEST(readable_erok == 0, "root: faccessat(readable R_OK AT_EACCESS) is unreadable = FAIL", readable_erok_errno)
TEST(writable_ewok == 0, "root: faccessat(writable W_OK AT_EACCESS) is unwritable = FAIL", writable_ewok_errno)
TEST(executable_exok == 0, "root: faccessat(executable X_OK AT_EACCESS) is unexecutable = FAIL", executable_exok_errno)
TEST(inaccessible_erok == 0, "root: faccessat(inaccessible R_OK AT_EACCESS) is unreadable = FAIL", inaccessible_erok_errno)
TEST(inaccessible_ewok == 0, "root: faccessat(inaccessible W_OK AT_EACCESS) is unwritable = FAIL", inaccessible_ewok_errno)
TEST(inaccessible_exok == -1, "root: faccessat(inaccessible X_OK AT_EACCESS) is executable = FAIL", inaccessible_exok_errno)
}
}
/* Test setuid root program */
/* Note: Must be setuid root, run as non-root, linked using absolute path */
else if (real_uid != 0 && effective_uid == 0)
{
TEST(readable_rok == -1, "setuid root: non-root user: faccessat(readable R_OK) is readable = FAIL", readable_rok_errno)
TEST(writable_wok == -1, "setuid root: non-root user: faccessat(writable W_OK) is writable = FAIL", writable_wok_errno)
TEST(executable_xok == -1, "setuid root: non-root user: faccessat(executable X_OK) is executable = FAIL", executable_xok_errno)
TEST(inaccessible_rok == -1, "setuid root: non-root user: faccessat(inaccessible R_OK) is readable = FAIL", inaccessible_rok_errno)
TEST(inaccessible_wok == -1, "setuid root: non-root user: faccessat(inaccessible W_OK) is writable = FAIL", inaccessible_wok_errno)
TEST(inaccessible_xok == -1, "setuid root: non-root user: faccessat(inaccessible X_OK) is executable = FAIL", inaccessible_xok_errno)
/* This is meaningful because uid != euid */
TEST(readable_erok == 0, "setuid root: non-root user: faccessat(readable R_OK AT_EACCESS) is unreadable = FAIL", readable_erok_errno)
TEST(writable_ewok == 0, "setuid root: non-root user: faccessat(writable W_OK AT_EACCESS) is unwritable = FAIL", writable_ewok_errno)
TEST(executable_exok == 0, "setuid root: non-root user: faccessat(executable X_OK AT_EACCESS) is unexecutable = FAIL", executable_exok_errno)
TEST(inaccessible_erok == 0, "setuid root: non-root user: faccessat(inaccessible R_OK AT_EACCESS) is unreadable = FAIL", inaccessible_erok_errno)
TEST(inaccessible_ewok == 0, "setuid root: non-root user: faccessat(inaccessible W_OK AT_EACCESS) is unwritable = FAIL", inaccessible_ewok_errno)
TEST(inaccessible_exok == -1, "setuid root: non-root user: faccessat(inaccessible X_OK AT_EACCESS) is executable = FAIL", inaccessible_exok_errno)
}
/* Test setuid non-root program */
/* Note: Must be setuid, run as non-root other user, linked using absolute path */
else if (real_uid != 0 && effective_uid != real_uid)
{
TEST(readable_rok == -1, "setuid non-root: non-root other user: faccessat(readable R_OK) is readable = FAIL", readable_rok_errno)
TEST(writable_wok == -1, "setuid non-root: non-root other user: faccessat(writable W_OK) is writable = FAIL", writable_wok_errno)
TEST(executable_xok == -1, "setuid non-root: non-root other user: faccessat(executable X_OK) is executable = FAIL", executable_xok_errno)
TEST(inaccessible_rok == -1, "setuid non-root: non-root other user: faccessat(inaccessible R_OK) is readable = FAIL", inaccessible_rok_errno)
TEST(inaccessible_wok == -1, "setuid non-root: non-root other user: faccessat(inaccessible W_OK) is writable = FAIL", inaccessible_wok_errno)
TEST(inaccessible_xok == -1, "setuid non-root: non-root other user: faccessat(inaccessible X_OK) is executable = FAIL", inaccessible_xok_errno)
/* This is meaningful because uid != euid */
TEST(readable_erok == 0, "setuid non-root: non-root other user: faccessat(readable R_OK AT_EACCESS) is unreadable = FAIL", readable_erok_errno)
TEST(writable_ewok == 0, "setuid non-root: non-root other user: faccessat(writable W_OK AT_EACCESS) is unwritable = FAIL", writable_ewok_errno)
TEST(executable_exok == 0, "setuid non-root: non-root other user: faccessat(executable X_OK AT_EACCESS) is unexecutable = FAIL", executable_exok_errno)
TEST(inaccessible_erok == -1, "setuid non-root: non-root other user: faccessat(inaccessible R_OK AT_EACCESS) is readable = FAIL", inaccessible_erok_errno)
TEST(inaccessible_ewok == -1, "setuid non-root: non-root other user: faccessat(inaccessible W_OK AT_EACCESS) is writable = FAIL", inaccessible_ewok_errno)
TEST(inaccessible_exok == -1, "setuid non-root: non-root other user: faccessat(inaccessible X_OK AT_EACCESS) is executable = FAIL", inaccessible_exok_errno)
}
/* Report any unexpected situation (won't happen) */
else
{
fprintf(stderr, "This uid/euid combination isn't currently tested.\n");
++failures;
}
/* Only show the test files and process permissions if there were failures */
if (failures)
{
fprintf(stderr, "faccessat_setuid: uid=%s euid=%s gid=%s egid=%s\n",
getpwuid(real_uid) ? getpwuid(real_uid)->pw_name : "?",
getpwuid(effective_uid) ? getpwuid(effective_uid)->pw_name : "?",
getgrgid(real_gid) ? getgrgid(real_gid)->gr_name : "?",
getgrgid(effective_gid) ? getgrgid(effective_gid)->gr_name : "?"
);
system("/bin/ls -l " TEST_TEMP "/tmp/*ble");
}
/* Test argument validation */
int check_pathname_rc = faccessat(AT_FDCWD, NULL, R_OK, 0);
int check_pathname_errno = errno;
int check_dirfd_rc = faccessat(-1, "pathname", R_OK, 0);
int check_dirfd_errno = errno;
int check_mode_rc = faccessat(AT_FDCWD, "pathname", -1, 0); // Apple doesn't check this
int check_mode_errno = errno;
int check_flag_rc = faccessat(AT_FDCWD, "pathname", R_OK, -1);
int check_flag_errno = errno;
TEST(check_pathname_rc == -1, "check pathname failed", 0)
if (check_pathname_rc == -1)
TEST(check_pathname_errno == EFAULT, "check pathname errno wrong (should be EFAULT)", check_pathname_errno)
TEST(check_dirfd_rc == -1, "check dirfd failed", 0)
if (check_dirfd_rc == -1) // On 10.14 this is EBADF. On 10.4 it's ENOENT
TEST(check_dirfd_errno == EBADF || check_dirfd_errno == ENOENT, "check dirfd errno wrong (should be EBADF or ENOENT)", check_dirfd_errno)
TEST(check_mode_rc == -1, "check mode failed", 0) // Apple doesn't check this argument - failure is ENOENT/EPERM
//if (check_mode_rc == -1)
// TEST(check_mode_errno == EINVAL, "check mode errno wrong (should be EINVAL)", check_mode_errno)
if (check_mode_rc == -1) // On 10.14 this is ENOENT. On 10.6 it's EPERM
TEST(check_mode_errno == ENOENT || check_mode_errno == EPERM, "check mode errno wrong (should be ENOENT or EPERM)", check_mode_errno)
TEST(check_flag_rc == -1, "check flag failed", 0)
if (check_flag_rc == -1)
TEST(check_flag_errno == EINVAL, "check flag errno wrong (should be EINVAL)", check_flag_errno)
/* Cleanup */
unlink(readable_path);
unlink(writable_path);
unlink(executable_path);
unlink(inaccessible_path);
if (mkdir_rc == 0)
rmdir(tmpdir);
/* Delete test/test_faccessat_setuid. Don't wait for make clean. */
if (real_uid != effective_uid || real_gid != effective_gid)
unlink(argv[0]);
printf("%s %s.\n", progname, failures ? "failed" : "passed");
return (failures) ? 1 : 0;
}