diff options
author | Tavian Barnes <tavianator@tavianator.com> | 2019-05-23 17:11:23 -0400 |
---|---|---|
committer | Tavian Barnes <tavianator@tavianator.com> | 2019-05-24 09:00:50 -0400 |
commit | d9b3196d6c8f4fa0e7d0a4771040762edaebb1ee (patch) | |
tree | f378d6955cf3cd6817a47e3a95284441552b221e | |
parent | 28bbaeac8058653a4a46ae439c37d251a550f4f9 (diff) | |
download | bfs-d9b3196d6c8f4fa0e7d0a4771040762edaebb1ee.tar.xz |
fsade: Refactor the POSIX.1e abstractions
Since we're going to want to abstract more things that aren't part of
POSIX.1e (like xattrs) in a similar way, let's give this a more generic
name. And while we're at it, give it some more precise error reporting,
and add some tests.
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | color.c | 4 | ||||
-rw-r--r-- | eval.c | 18 | ||||
-rw-r--r-- | fsade.c | 232 | ||||
-rw-r--r-- | fsade.h (renamed from posix1e.h) | 32 | ||||
-rw-r--r-- | parse.c | 12 | ||||
-rw-r--r-- | posix1e.c | 192 | ||||
-rwxr-xr-x | tests.sh | 52 | ||||
-rw-r--r-- | tests/test_L_acl.out | 2 | ||||
-rw-r--r-- | tests/test_L_capable.out | 2 | ||||
-rw-r--r-- | tests/test_acl.out | 1 | ||||
-rw-r--r-- | tests/test_capable.out | 1 |
12 files changed, 334 insertions, 216 deletions
@@ -73,11 +73,11 @@ bfs: \ dstring.o \ eval.o \ exec.o \ + fsade.o \ main.o \ mtab.o \ opt.o \ parse.o \ - posix1e.o \ printf.o \ spawn.o \ stat.o \ @@ -17,7 +17,7 @@ #include "color.h" #include "bftw.h" #include "dstring.h" -#include "posix1e.h" +#include "fsade.h" #include "stat.h" #include "trie.h" #include "util.h" @@ -572,7 +572,7 @@ static const char *file_color(const struct colors *colors, const char *filename, color = colors->setuid; } else if (colors->setgid && (statbuf->mode & 02000)) { color = colors->setgid; - } else if (colors->capable && bfs_check_capabilities(ftwbuf)) { + } else if (colors->capable && bfs_check_capabilities(ftwbuf) > 0) { color = colors->capable; } else if (colors->executable && (statbuf->mode & 00111)) { color = colors->executable; @@ -25,8 +25,8 @@ #include "diag.h" #include "dstring.h" #include "exec.h" +#include "fsade.h" #include "mtab.h" -#include "posix1e.h" #include "printf.h" #include "stat.h" #include "trie.h" @@ -160,14 +160,26 @@ bool eval_access(const struct expr *expr, struct eval_state *state) { * -acl test. */ bool eval_acl(const struct expr *expr, struct eval_state *state) { - return bfs_check_acl(state->ftwbuf); + int ret = bfs_check_acl(state->ftwbuf); + if (ret >= 0) { + return ret; + } else { + eval_report_error(state); + return false; + } } /** * -capable test. */ bool eval_capable(const struct expr *expr, struct eval_state *state) { - return bfs_check_capabilities(state->ftwbuf); + int ret = bfs_check_capabilities(state->ftwbuf); + if (ret >= 0) { + return ret; + } else { + eval_report_error(state); + return false; + } } /** @@ -0,0 +1,232 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2019 Tavian Barnes <tavianator@tavianator.com> * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * 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 "fsade.h" +#include "bftw.h" +#include "dstring.h" +#include "util.h" +#include <assert.h> +#include <errno.h> +#include <unistd.h> + +#if BFS_CAN_CHECK_ACL +# include <sys/acl.h> +#endif + +#if BFS_CAN_CHECK_CAPABILITIES +# include <sys/capability.h> +#endif + +#if BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES + +/** + * Many of the APIs used here don't have *at() variants, but we can try to + * emulate something similar if /proc/self/fd is available. + */ +static const char *fake_at(const struct BFTW *ftwbuf) { + static bool proc_works = true; + static bool proc_checked = false; + + char *path = NULL; + if (!proc_works || ftwbuf->at_fd == AT_FDCWD) { + goto fail; + } + + path = dstrprintf("/proc/self/fd/%d/", ftwbuf->at_fd); + if (!path) { + goto fail; + } + + if (!proc_checked) { + proc_checked = true; + if (xfaccessat(AT_FDCWD, path, F_OK) != 0) { + proc_works = false; + goto fail; + } + } + + if (dstrcat(&path, ftwbuf->at_path) != 0) { + goto fail; + } + + return path; + +fail: + dstrfree(path); + return ftwbuf->path; +} + +static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { + if (path != ftwbuf->path) { + dstrfree((char *)path); + } +} + +/** + * Check if an error was caused by the absence of support or data for a feature. + */ +static bool is_absence_error(int error) { + // If the OS doesn't support the feature, it's obviously not enabled for + // any files + if (error == ENOTSUP) { + return true; + } + + // On Linux, ACLs and capabilities are implemented in terms of extended + // attributes, which report ENODATA/ENOATTR when missing + +#ifdef ENODATA + if (error == ENODATA) { + return true; + } +#endif + +#if defined(ENOATTR) && ENOATTR != ENODATA + if (error == ENOATTR) { + return true; + } +#endif + + return false; +} + +#endif // BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES + +#if BFS_CAN_CHECK_ACL + +/** Check if any ACLs of the given type are non-trivial. */ +static int bfs_check_acl_type(const char *path, acl_type_t type) { + acl_t acl = acl_get_file(path, type); + if (!acl) { + if (is_absence_error(errno)) { + return 0; + } else { + return -1; + } + } + + int ret = 0; + acl_entry_t entry; + for (int status = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); + status > 0; + status = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) { +#if defined(ACL_USER_OBJ) && defined(ACL_GROUP_OBJ) && defined(ACL_OTHER) + acl_tag_t tag; + if (acl_get_tag_type(entry, &tag) != 0) { + continue; + } + if (tag != ACL_USER_OBJ && tag != ACL_GROUP_OBJ && tag != ACL_OTHER) { + ret = 1; + break; + } +#else + ret = 1; + break; +#endif + } + + acl_free(acl); + return ret; +} + +int bfs_check_acl(const struct BFTW *ftwbuf) { + if (ftwbuf->typeflag == BFTW_LNK) { + return 0; + } + + const char *path = fake_at(ftwbuf); + + int error = 0; + int ret = bfs_check_acl_type(path, ACL_TYPE_ACCESS); + if (ret < 0) { + error = errno; + } + + if (ret <= 0 && ftwbuf->typeflag == BFTW_DIR) { + ret = bfs_check_acl_type(path, ACL_TYPE_DEFAULT); + if (ret < 0) { + error = errno; + } + } + +#ifdef ACL_TYPE_EXTENDED + if (ret <= 0) { + ret = bfs_check_acl_type(path, ACL_TYPE_EXTENDED); + if (ret < 0) { + error = errno; + } + } +#endif + + free_fake_at(ftwbuf, path); + errno = error; + return ret; +} + +#else // !BFS_CAN_CHECK_ACL + +int bfs_check_acl(const struct BFTW *ftwbuf) { + errno = ENOTSUP; + return -1; +} + +#endif + +#if BFS_CAN_CHECK_CAPABILITIES + +int bfs_check_capabilities(const struct BFTW *ftwbuf) { + if (ftwbuf->typeflag == BFTW_LNK) { + return 0; + } + + int ret = -1, error; + const char *path = fake_at(ftwbuf); + + cap_t caps = cap_get_file(path); + if (!caps) { + error = errno; + if (is_absence_error(error)) { + ret = 0; + } + goto out_path; + } + + // TODO: Any better way to check for a non-empty capability set? + char *text = cap_to_text(caps, NULL); + if (!text) { + error = errno; + goto out_caps; + } + ret = text[0] ? 1 : 0; + + error = errno; + cap_free(text); +out_caps: + cap_free(caps); +out_path: + free_fake_at(ftwbuf, path); + errno = error; + return ret; +} + +#else // !BFS_CAN_CHECK_CAPABILITIES + +int bfs_check_capabilities(const struct BFTW *ftwbuf) { + errno = ENOTSUP; + return -1; +} + +#endif @@ -15,34 +15,44 @@ ****************************************************************************/ /** - * The withdrawn POSIX.1e standard specified a security API for POSIX systems. - * Although it was never ratified, many of its interfaces are widely deployed - * in Unix-like systems. These functions wrap the POSIX.1e APIs if present, - * to support things like Access Control Lists and Capabilities. + * A facade over (file)system features that are (un)implemented differently + * between platforms. */ -#ifndef BFS_POSIX1E_H -#define BFS_POSIX1E_H +#ifndef BFS_FSADE_H +#define BFS_FSADE_H #include "bftw.h" #include "util.h" #include <stdbool.h> -#if !defined(BFS_HAS_POSIX1E_CAPABILITIES) && BFS_HAS_SYS_CAPABILITY && !__FreeBSD__ +#define BFS_CAN_CHECK_ACL BFS_HAS_SYS_ACL + +#if !defined(BFS_CAN_CHECK_CAPABILITIES) && BFS_HAS_SYS_CAPABILITY && !__FreeBSD__ # include <sys/capability.h> # ifdef CAP_CHOWN -# define BFS_HAS_POSIX1E_CAPABILITIES true +# define BFS_CAN_CHECK_CAPABILITIES true # endif #endif /** * Check if a file has a non-trvial Access Control List. + * + * @param ftwbuf + * The file to check. + * @return + * 1 if it does, 0 if it doesn't, or -1 if an error occurred. */ -bool bfs_check_acl(const struct BFTW *ftwbuf); +int bfs_check_acl(const struct BFTW *ftwbuf); /** * Check if a file has a non-trvial capability set. + * + * @param ftwbuf + * The file to check. + * @return + * 1 if it does, 0 if it doesn't, or -1 if an error occurred. */ -bool bfs_check_capabilities(const struct BFTW *ftwbuf); +int bfs_check_capabilities(const struct BFTW *ftwbuf); -#endif // BFS_POSIX1E_H +#endif // BFS_FSADE_H @@ -28,8 +28,8 @@ #include "eval.h" #include "exec.h" #include "expr.h" +#include "fsade.h" #include "mtab.h" -#include "posix1e.h" #include "printf.h" #include "stat.h" #include "typo.h" @@ -999,12 +999,11 @@ static struct expr *parse_access(struct parser_state *state, int flag, int arg2) * Parse -acl. */ static struct expr *parse_acl(struct parser_state *state, int flag, int arg2) { -#if BFS_HAS_SYS_ACL +#if BFS_CAN_CHECK_ACL struct expr *expr = parse_nullary_test(state, eval_acl); if (expr) { expr->cost = STAT_COST; expr->probability = 0.00002; - expr->ephemeral_fds = 1; } return expr; #else @@ -1057,12 +1056,11 @@ static struct expr *parse_time(struct parser_state *state, int field, int unit) * Parse -capable. */ static struct expr *parse_capable(struct parser_state *state, int flag, int arg2) { -#if BFS_HAS_POSIX1E_CAPABILITIES +#if BFS_CAN_CHECK_CAPABILITIES struct expr *expr = parse_nullary_test(state, eval_capable); if (expr) { expr->cost = STAT_COST; expr->probability = 0.000002; - expr->ephemeral_fds = 1; } return expr; #else @@ -2427,7 +2425,7 @@ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) { cfprintf(cout, "${bld}Tests:${rs}\n\n"); -#if BFS_HAS_SYS_ACL +#if BFS_CAN_CHECK_ACL cfprintf(cout, " ${blu}-acl${rs}\n"); cfprintf(cout, " Find files with non-trivial Access Control Lists\n"); #endif @@ -2438,7 +2436,7 @@ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) { "modified\n"); cfprintf(cout, " ${blu}-${rs}[${blu}aBcm${rs}]${blu}time${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files ${blu}a${rs}ccessed/${blu}B${rs}irthed/${blu}c${rs}hanged/${blu}m${rs}odified ${bld}N${rs} days ago\n"); -#if BFS_HAS_POSIX1E_CAPABILITIES +#if BFS_CAN_CHECK_CAPABILITIES cfprintf(cout, " ${blu}-capable${rs}\n"); cfprintf(cout, " Match files with POSIX.1e capabilities set\n"); #endif diff --git a/posix1e.c b/posix1e.c deleted file mode 100644 index a898b39..0000000 --- a/posix1e.c +++ /dev/null @@ -1,192 +0,0 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019 Tavian Barnes <tavianator@tavianator.com> * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * 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 "posix1e.h" -#include "bftw.h" -#include "util.h" -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#if BFS_HAS_SYS_ACL -# include <sys/acl.h> -#endif - -#if BFS_HAS_POSIX1E_CAPABILITIES -# include <sys/capability.h> -#endif - -#if BFS_HAS_SYS_ACL || BFS_HAS_POSIX1E_CAPABILITIES - -static const char *open_path(const struct BFTW *ftwbuf, int *fd) { -#ifdef O_PATH - // The POSIX.1e APIS predate the *at() family of functions. We'd still - // like to do something to avoid path re-traversals and limit races - // though. Ideally we could just do openat(..., O_PATH) (since we may - // not have read access) and pass that fd to something like cap_get_fd() - // but that will fail since fgetxattr() needs read access to the file. - // The workaround is to use O_PATH to open an fd and then pass - // /proc/self/fd/<fd> to cap_get_path(). Inspired by - // https://android.googlesource.com/platform/bionic/+/2825f10b7f61558c264231a536cf3affc0d84204 - int flags = O_PATH; - if (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW) { - flags |= O_NOFOLLOW; - } - - *fd = openat(ftwbuf->at_fd, ftwbuf->at_path, flags); - if (*fd < 0) { - return NULL; - } - - size_t size = strlen("/proc/self/fd/") + CHAR_BIT*sizeof(int) + 1; - char *path = malloc(size); - if (!path) { - close(*fd); - *fd = -1; - return NULL; - } - - snprintf(path, size, "/proc/self/fd/%d", *fd); - return path; -#else - *fd = -1; - return ftwbuf->path; -#endif -} - -static void close_path(const struct BFTW *ftwbuf, const char *path, int fd) { - if (path && path != ftwbuf->path) { - free((void *)path); - } - if (fd >= 0) { - close(fd); - } -} - -#endif // BFS_HAS_SYS_ACL || BFS_HAS_POSIX1E_CAPABILITIES - -#if BFS_HAS_SYS_ACL - -/** Check if any ACLs of the given type are non-trivial. */ -static bool bfs_check_acl_type(const char *path, acl_type_t type) { - acl_t acl = acl_get_file(path, type); - if (!acl) { - return false; - } - - bool ret = false; - acl_entry_t entry; - for (int status = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); - status > 0; - status = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) { -#if defined(ACL_USER_OBJ) && defined(ACL_GROUP_OBJ) && defined(ACL_OTHER) - acl_tag_t tag; - if (acl_get_tag_type(entry, &tag) != 0) { - continue; - } - if (tag != ACL_USER_OBJ && tag != ACL_GROUP_OBJ && tag != ACL_OTHER) { - ret = true; - break; - } -#else - ret = true; - break; -#endif - } - - acl_free(acl); - return ret; -} - -bool bfs_check_acl(const struct BFTW *ftwbuf) { - if (ftwbuf->typeflag == BFTW_LNK) { - return false; - } - - int fd; - const char *path = open_path(ftwbuf, &fd); - if (!path) { - return false; - } - - bool ret = false; - if (bfs_check_acl_type(path, ACL_TYPE_ACCESS)) { - ret = true; - } else if (bfs_check_acl_type(path, ACL_TYPE_DEFAULT)) { - ret = true; -#ifdef ACL_TYPE_EXTENDED - } else if (bfs_check_acl_type(path, ACL_TYPE_EXTENDED)) { - ret = true; -#endif - } - - close_path(ftwbuf, path, fd); - return ret; -} - -#else // !BFS_HAS_SYS_ACL - -bool bfs_check_acl(const struct BFTW *ftwbuf) { - return false; -} - -#endif - -#if BFS_HAS_POSIX1E_CAPABILITIES - -bool bfs_check_capabilities(const struct BFTW *ftwbuf) { - bool ret = false; - - if (ftwbuf->typeflag == BFTW_LNK) { - goto out; - } - - int fd; - const char *path = open_path(ftwbuf, &fd); - if (!path) { - goto out; - } - - cap_t caps = cap_get_file(path); - if (!caps) { - goto out_close; - } - - // TODO: Any better way to check for a non-empty capability set? - char *text = cap_to_text(caps, NULL); - if (!text) { - goto out_free_caps; - } - ret = text[0]; - - cap_free(text); -out_free_caps: - cap_free(caps); -out_close: - close_path(ftwbuf, path, fd); -out: - return ret; -} - -#else // !BFS_HAS_POSIX1E_CAPABILITIES - -bool bfs_check_capabilities(const struct BFTW *ftwbuf) { - return false; -} - -#endif @@ -279,6 +279,9 @@ bsd_tests=( # Primaries + test_acl + test_L_acl + test_delete test_depth_maxdepth_1 @@ -642,6 +645,9 @@ bfs_tests=( ) sudo_tests=( + test_capable + test_L_capable + test_mount test_xdev @@ -2299,6 +2305,52 @@ function test_xtype_bind_mount() { return $ret } +function test_acl() { + rm -rf scratch/* + + if ! invoke_bfs scratch -acl 2>/dev/null; then + return 0 + fi + + touch scratch/{normal,acl} + setfacl -m "u:$(id -un):rw" scratch/acl + ln -s acl scratch/link + + bfs_diff scratch -acl +} + +function test_L_acl() { + rm -rf scratch/* + + if ! invoke_bfs scratch -acl 2>/dev/null; then + return 0 + fi + + touch scratch/{normal,acl} + setfacl -m "u:$(id -un):rw" scratch/acl + ln -s acl scratch/link + + bfs_diff -L scratch -acl +} + +function test_capable() { + rm -rf scratch/* + touch scratch/{normal,capable} + sudo setcap all+ep scratch/capable + ln -s capable scratch/link + + bfs_diff scratch -capable +} + +function test_L_capable() { + rm -rf scratch/* + touch scratch/{normal,capable} + sudo setcap all+ep scratch/capable + ln -s capable scratch/link + + bfs_diff -L scratch -capable +} + BOL= EOL='\n' diff --git a/tests/test_L_acl.out b/tests/test_L_acl.out new file mode 100644 index 0000000..1dae00a --- /dev/null +++ b/tests/test_L_acl.out @@ -0,0 +1,2 @@ +scratch/acl +scratch/link diff --git a/tests/test_L_capable.out b/tests/test_L_capable.out new file mode 100644 index 0000000..e5ba3c7 --- /dev/null +++ b/tests/test_L_capable.out @@ -0,0 +1,2 @@ +scratch/capable +scratch/link diff --git a/tests/test_acl.out b/tests/test_acl.out new file mode 100644 index 0000000..ddf8446 --- /dev/null +++ b/tests/test_acl.out @@ -0,0 +1 @@ +scratch/acl diff --git a/tests/test_capable.out b/tests/test_capable.out new file mode 100644 index 0000000..78b5bd9 --- /dev/null +++ b/tests/test_capable.out @@ -0,0 +1 @@ +scratch/capable |