From dcdad299479e2d224448cd8a09a33afc1328aa13 Mon Sep 17 00:00:00 2001 From: Mariusz Zaborski Date: Sun, 10 Jan 2021 12:44:06 +0100 Subject: [PATCH] fileargs: add support for realpath --- lib/libcasper/services/cap_fileargs/Makefile | 1 + .../services/cap_fileargs/cap_fileargs.3 | 13 +- .../services/cap_fileargs/cap_fileargs.c | 62 +++++++- .../services/cap_fileargs/cap_fileargs.h | 6 + .../cap_fileargs/tests/fileargs_test.c | 145 ++++++++++++++++++ 5 files changed, 224 insertions(+), 3 deletions(-) diff --git a/lib/libcasper/services/cap_fileargs/Makefile b/lib/libcasper/services/cap_fileargs/Makefile index 04787c01db35..22230f82d9f4 100644 --- a/lib/libcasper/services/cap_fileargs/Makefile +++ b/lib/libcasper/services/cap_fileargs/Makefile @@ -35,5 +35,6 @@ MLINKS+=cap_fileargs.3 fileargs_init.3 MLINKS+=cap_fileargs.3 fileargs_initnv.3 MLINKS+=cap_fileargs.3 fileargs_lstat.3 MLINKS+=cap_fileargs.3 fileargs_open.3 +MLINKS+=cap_fileargs.3 fileargs_realpath.3 .include diff --git a/lib/libcasper/services/cap_fileargs/cap_fileargs.3 b/lib/libcasper/services/cap_fileargs/cap_fileargs.3 index b59d30b2d595..acf51e4ed62b 100644 --- a/lib/libcasper/services/cap_fileargs/cap_fileargs.3 +++ b/lib/libcasper/services/cap_fileargs/cap_fileargs.3 @@ -24,7 +24,7 @@ .\" .\" $FreeBSD$ .\" -.Dd May 5, 2020 +.Dd January 10, 2021 .Dt CAP_FILEARGS 3 .Os .Sh NAME @@ -59,6 +59,8 @@ .Fn fileargs_open "fileargs_t *fa" "const char *name" .Ft "FILE *" .Fn fileargs_fopen "fileargs_t *fa" "const char *name" "const char *mode" +.Ft "char *" +.Fn fileargs_realpath "fileargs_t *fa" "const char *pathname" "char *reserved_path" .Sh DESCRIPTION The library is used to simplify Capsicumizing a tools that are using file system. Idea behind the library is that we are passing a remaining @@ -115,6 +117,9 @@ and .It FA_LSTAT Allow .Fn fileargs_lstat . +.It FA_REALPATH +Allow +.Fn fileargs_realpath . .El .Pp The function @@ -161,6 +166,11 @@ and expect that all arguments are fetched from the .Va fileargs_t structure. +.Pp +The function +.Fn fileargs_realpath +is equivalent to +.Xr realpath 3 . .Sh LIMITS This section describe which values and types should be used to pass arguments to the .Fa system.fileargs @@ -261,6 +271,7 @@ fileargs_free(fa); .Xr err 3 , .Xr fopen 3 , .Xr getopt 3 , +.Xr realpath 3 , .Xr capsicum 4 , .Xr nv 9 .Sh HISTORY diff --git a/lib/libcasper/services/cap_fileargs/cap_fileargs.c b/lib/libcasper/services/cap_fileargs/cap_fileargs.c index a777647b1720..ecab23004fcf 100644 --- a/lib/libcasper/services/cap_fileargs/cap_fileargs.c +++ b/lib/libcasper/services/cap_fileargs/cap_fileargs.c @@ -1,7 +1,7 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * - * Copyright (c) 2018 Mariusz Zaborski + * Copyright (c) 2018-2021 Mariusz Zaborski * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -412,6 +412,41 @@ fileargs_lstat(fileargs_t *fa, const char *name, struct stat *sb) return (0); } +char * +fileargs_realpath(fileargs_t *fa, const char *pathname, char *reserved_path) +{ + nvlist_t *nvl; + char *ret; + + assert(fa != NULL); + assert(fa->fa_magic == FILEARGS_MAGIC); + + if (pathname == NULL) { + errno = EINVAL; + return (NULL); + } + + if (fa->fa_chann == NULL) { + errno = ENOTCAPABLE; + return (NULL); + } + + nvl = fileargs_fetch(fa, pathname, "realpath"); + if (nvl == NULL) + return (NULL); + + if (reserved_path != NULL) { + ret = reserved_path; + strcpy(reserved_path, + nvlist_get_string(nvl, "realpath")); + } else { + ret = nvlist_take_string(nvl, "realpath"); + } + nvlist_destroy(nvl); + + return (ret); +} + void fileargs_free(fileargs_t *fa) { @@ -631,6 +666,28 @@ fileargs_command_lstat(const nvlist_t *limits, nvlist_t *nvlin, return (0); } +static int +fileargs_command_realpath(const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + const char *pathname; + char *resolvedpath; + + if (limits == NULL) + return (ENOTCAPABLE); + + if (!fileargs_allowed(limits, nvlin, FA_REALPATH)) + return (ENOTCAPABLE); + + pathname = nvlist_get_string(nvlin, "name"); + resolvedpath = realpath(pathname, NULL); + if (resolvedpath == NULL) + return (errno); + + nvlist_move_string(nvlout, "realpath", resolvedpath); + return (0); +} + static int fileargs_command_open(const nvlist_t *limits, nvlist_t *nvlin, nvlist_t *nvlout) @@ -668,9 +725,10 @@ fileargs_command(const char *cmd, const nvlist_t *limits, if (strcmp(cmd, "open") == 0) return (fileargs_command_open(limits, nvlin, nvlout)); - if (strcmp(cmd, "lstat") == 0) return (fileargs_command_lstat(limits, nvlin, nvlout)); + if (strcmp(cmd, "realpath") == 0) + return (fileargs_command_realpath(limits, nvlin, nvlout)); return (EINVAL); } diff --git a/lib/libcasper/services/cap_fileargs/cap_fileargs.h b/lib/libcasper/services/cap_fileargs/cap_fileargs.h index 03ff5c29d6c0..6e8523cb9423 100644 --- a/lib/libcasper/services/cap_fileargs/cap_fileargs.h +++ b/lib/libcasper/services/cap_fileargs/cap_fileargs.h @@ -39,6 +39,7 @@ #define FA_OPEN 1 #define FA_LSTAT 2 +#define FA_REALPATH 4 #ifdef WITH_CASPER struct fileargs; @@ -55,6 +56,8 @@ fileargs_t *fileargs_initnv(nvlist_t *limits); fileargs_t *fileargs_cinitnv(cap_channel_t *cas, nvlist_t *limits); int fileargs_lstat(fileargs_t *fa, const char *name, struct stat *sb); int fileargs_open(fileargs_t *fa, const char *name); +char *fileargs_realpath(fileargs_t *fa, const char *pathname, + char *reserved_path); void fileargs_free(fileargs_t *fa); FILE *fileargs_fopen(fileargs_t *fa, const char *name, const char *mode); @@ -117,6 +120,9 @@ fileargs_cinitnv(cap_channel_t *cas __unused, nvlist_t *limits) lstat(name, sb) #define fileargs_open(fa, name) \ open(name, fa->fa_flags, fa->fa_mode) +#define fileargs_realpath(fa, pathname, reserved_path) \ + realpath(pathname, reserved_path) + static inline FILE *fileargs_fopen(fileargs_t *fa, const char *name, const char *mode) { diff --git a/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c b/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c index ad889bb2986f..9a7f9dfcb9aa 100644 --- a/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c +++ b/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c @@ -141,6 +141,57 @@ test_file_lstat(fileargs_t *fa, const char *file) return (0); } +static int +test_file_realpath_static(fileargs_t *fa, const char *file) +{ + char fapath[PATH_MAX], origpath[PATH_MAX]; + + if (fileargs_realpath(fa, file, fapath) == NULL) + return (errno); + + ATF_REQUIRE(realpath(file, origpath) != NULL); + + if (strcmp(fapath, origpath) != 0) + return (EINVAL); + + return (0); +} + +static int +test_file_realpath_alloc(fileargs_t *fa, const char *file) +{ + char *fapath, *origpath; + int serrno; + + fapath = fileargs_realpath(fa, file, NULL); + if (fapath == NULL) + return (errno); + + origpath = realpath(file, NULL); + ATF_REQUIRE(origpath != NULL); + + serrno = 0; + if (strcmp(fapath, origpath) != 0) + serrno = EINVAL; + + free(fapath); + free(origpath); + + return (serrno); +} + +static int +test_file_realpath(fileargs_t *fa, const char *file) +{ + int serrno; + + serrno = test_file_realpath_static(fa, file); + if (serrno != 0) + return serrno; + + return (test_file_realpath_alloc(fa, file)); +} + static int test_file_mode(int fd, int mode) { @@ -254,6 +305,8 @@ ATF_TC_BODY(fileargs__open_read, tc) ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); ATF_REQUIRE(test_file_cap(fd, &norights) == false); ATF_REQUIRE(test_file_write(fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); /* CLOSE */ ATF_REQUIRE(close(fd) == 0); @@ -297,6 +350,8 @@ ATF_TC_BODY(fileargs__open_write, tc) ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); ATF_REQUIRE(test_file_cap(fd, &norights) == false); ATF_REQUIRE(test_file_read(fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); /* CLOSE */ ATF_REQUIRE(close(fd) == 0); @@ -337,6 +392,8 @@ ATF_TC_BODY(fileargs__open_create, tc) ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); /* CLOSE */ ATF_REQUIRE(close(fd) == 0); @@ -417,6 +474,8 @@ ATF_TC_BODY(fileargs__fopen_read, tc) ENOTCAPABLE); ATF_REQUIRE(test_file_cap(fd, &norights) == false); ATF_REQUIRE(test_file_fwrite(pfile) == EBADF); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); /* CLOSE */ ATF_REQUIRE(fclose(pfile) == 0); @@ -463,6 +522,8 @@ ATF_TC_BODY(fileargs__fopen_write, tc) ENOTCAPABLE); ATF_REQUIRE(test_file_cap(fd, &norights) == false); ATF_REQUIRE(test_file_fread(pfile) == EBADF); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); /* CLOSE */ ATF_REQUIRE(fclose(pfile) == 0); @@ -504,6 +565,8 @@ ATF_TC_BODY(fileargs__fopen_create, tc) ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); ATF_REQUIRE(test_file_fopen(fa, TEST_FILE, "w+", NULL) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); /* CLOSE */ ATF_REQUIRE(fclose(pfile) == 0); @@ -535,6 +598,8 @@ ATF_TC_BODY(fileargs__lstat, tc) ATF_REQUIRE(test_file_open(fa, files[i], &fd) == ENOTCAPABLE); ATF_REQUIRE(test_file_lstat(fa, TEST_FILE) == ENOTCAPABLE); ATF_REQUIRE(test_file_open(fa, TEST_FILE, &fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); } } ATF_TC_CLEANUP(fileargs__lstat, tc) @@ -542,6 +607,36 @@ ATF_TC_CLEANUP(fileargs__lstat, tc) clear_files(); } +ATF_TC_WITH_CLEANUP(fileargs__realpath); +ATF_TC_HEAD(fileargs__realpath, tc) {} +ATF_TC_BODY(fileargs__realpath, tc) +{ + fileargs_t *fa; + size_t i; + int fd; + + prepare_files(MAX_FILES, true); + + fa = fileargs_init(MAX_FILES, files, 0, 0, NULL, FA_REALPATH); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + ATF_REQUIRE(test_file_realpath(fa, files[i]) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_lstat(fa, TEST_FILE) == ENOTCAPABLE); + ATF_REQUIRE(test_file_open(fa, TEST_FILE, &fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); + } +} +ATF_TC_CLEANUP(fileargs__realpath, tc) +{ + clear_files(); +} + ATF_TC_WITH_CLEANUP(fileargs__open_lstat); ATF_TC_HEAD(fileargs__open_lstat, tc) {} ATF_TC_BODY(fileargs__open_lstat, tc) @@ -576,6 +671,8 @@ ATF_TC_BODY(fileargs__open_lstat, tc) ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); ATF_REQUIRE(test_file_cap(fd, &norights) == false); ATF_REQUIRE(test_file_write(fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); /* CLOSE */ ATF_REQUIRE(close(fd) == 0); @@ -586,6 +683,51 @@ ATF_TC_CLEANUP(fileargs__open_lstat, tc) clear_files(); } +ATF_TC_WITH_CLEANUP(fileargs__open_realpath); +ATF_TC_HEAD(fileargs__open_realpath, tc) {} +ATF_TC_BODY(fileargs__open_realpath, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + int fd; + + prepare_files(MAX_FILES, true); + + cap_rights_init(&rights, CAP_READ | CAP_FCNTL); + cap_rights_init(&norights, CAP_WRITE); + fa = fileargs_init(MAX_FILES, files, O_RDONLY, 0, &rights, + FA_OPEN | FA_REALPATH); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We open file twice to check if we can. */ + ATF_REQUIRE(test_file_realpath(fa, files[i]) == 0); + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(close(fd) == 0); + + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == 0); + ATF_REQUIRE(test_file_mode(fd, O_RDONLY) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_read(fd) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_write(fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(close(fd) == 0); + } +} +ATF_TC_CLEANUP(fileargs__open_realpath, tc) +{ + clear_files(); +} + ATF_TP_ADD_TCS(tp) { @@ -600,7 +742,10 @@ ATF_TP_ADD_TCS(tp) ATF_TP_ADD_TC(tp, fileargs__lstat); + ATF_TP_ADD_TC(tp, fileargs__realpath); + ATF_TP_ADD_TC(tp, fileargs__open_lstat); + ATF_TP_ADD_TC(tp, fileargs__open_realpath); return (atf_no_error()); }