From ac98e0f82027b3b01d70a36cd7a5a42f80ca7ebf Mon Sep 17 00:00:00 2001 From: Arden97 Date: Wed, 26 Aug 2026 17:11:38 +0200 Subject: [PATCH 1/3] get file descriptor before getting attributes for filecontent probes --- .../independent/textfilecontent54_probe.c | 33 ++++++++++--------- .../independent/textfilecontent_probe.c | 31 ++++++++--------- .../independent/yamlfilecontent_probe.c | 24 ++++++++++++-- src/OVAL/probes/probe-api.c | 20 +++++++++++ src/OVAL/probes/public/probe-api.h | 8 +++++ 5 files changed, 84 insertions(+), 32 deletions(-) diff --git a/src/OVAL/probes/independent/textfilecontent54_probe.c b/src/OVAL/probes/independent/textfilecontent54_probe.c index 24d766b918..05fb8ba3a0 100644 --- a/src/OVAL/probes/independent/textfilecontent54_probe.c +++ b/src/OVAL/probes/independent/textfilecontent54_probe.c @@ -151,23 +151,13 @@ static int process_file(const char *prefix, const char *path, const char *file, if (probe_path_is_blocked(whole_path, blocked_paths)) { goto cleanup; } - /* - * If stat() fails, don't report an error and just skip the file. - * This is an expected situation, because the fts_*() functions - * are called with the 'FTS_PHYSICAL' option. Normally, stumbling - * upon a symlink without a target would cause fts_read() to return - * the 'FTS_SLNONE' flag, but the 'FTS_PHYSICAL' option causes it - * to return 'FTS_SL' and the presence of a valid target has to - * be determined with stat(). - */ whole_path_with_prefix = oscap_path_join(prefix, whole_path); - if (stat(whole_path_with_prefix, &st) == -1) - goto cleanup; - if (!S_ISREG(st.st_mode)) - goto cleanup; - - fd = open(whole_path_with_prefix, O_RDONLY); + fd = open(whole_path_with_prefix, O_RDONLY | O_NONBLOCK); if (fd == -1) { + if (errno == ENOENT || errno == EACCES || errno == ENOTDIR + || errno == ENAMETOOLONG || errno == ELOOP) + goto cleanup; + SEXP_t *msg; msg = probe_msg_creatf(OVAL_MESSAGE_LEVEL_ERROR, "open(): '%s' %s.", whole_path, strerror(errno)); @@ -177,6 +167,19 @@ static int process_file(const char *prefix, const char *path, const char *file, ret = -1; goto cleanup; } + /* + * If fstat() fails, don't report an error and just skip the file. + * This is an expected situation, because the fts_*() functions + * are called with the 'FTS_PHYSICAL' option. Normally, stumbling + * upon a symlink without a target would cause fts_read() to return + * the 'FTS_SLNONE' flag, but the 'FTS_PHYSICAL' option causes it + * to return 'FTS_SL' and the presence of a valid target has to + * be determined with fstat(). + */ + if (fstat(fd, &st) == -1 + || !S_ISREG(st.st_mode) + || probe_fd_path_is_blocked(fd, blocked_paths)) + goto cleanup; do { buf_size += buf_inc; diff --git a/src/OVAL/probes/independent/textfilecontent_probe.c b/src/OVAL/probes/independent/textfilecontent_probe.c index 8689114001..f8efba8410 100644 --- a/src/OVAL/probes/independent/textfilecontent_probe.c +++ b/src/OVAL/probes/independent/textfilecontent_probe.c @@ -58,9 +58,11 @@ #include #include +#include #include #include #include +#include #include #include "_seap.h" @@ -139,7 +141,7 @@ struct pfdata { static int process_file(const char *prefix, const char *path, const char *filename, void *arg, oval_schema_version_t over, struct oscap_list *blocked_paths) { struct pfdata *pfd = (struct pfdata *) arg; - int ret = 0, path_len, filename_len; + int ret = 0, path_len, filename_len, tmp_fd = -1; char *whole_path = NULL, *whole_path_with_prefix = NULL; FILE *fp = NULL; struct stat st; @@ -173,27 +175,24 @@ static int process_file(const char *prefix, const char *path, const char *filena if (probe_path_is_blocked(whole_path, blocked_paths)) { goto cleanup; } - - /* - * If stat() fails, don't report an error and just skip the file. - * This is an expected situation, because the fts_*() functions - * are called with the 'FTS_PHYSICAL' option. Normally, stumbling - * upon a symlink without a target would cause fts_read() to return - * the 'FTS_SLNONE' flag, but the 'FTS_PHYSICAL' option causes it - * to return 'FTS_SL' and the presence of a valid target has to - * be determined with stat(). - */ whole_path_with_prefix = oscap_path_join(prefix, whole_path); - if (stat(whole_path_with_prefix, &st) == -1) + tmp_fd = open(whole_path_with_prefix, O_RDONLY | O_NONBLOCK); + if (tmp_fd == -1) { + if (errno != ENOENT && errno != EACCES && errno != ENOTDIR + && errno != ENAMETOOLONG && errno != ELOOP) + ret = -2; goto cleanup; - if (!S_ISREG(st.st_mode)) + } + if (fstat(tmp_fd, &st) == -1 + || !S_ISREG(st.st_mode) + || probe_fd_path_is_blocked(tmp_fd, blocked_paths)) goto cleanup; - - fp = fopen(whole_path_with_prefix, "rb"); + fp = fdopen(tmp_fd, "rb"); if (fp == NULL) { ret = -2; goto cleanup; } + tmp_fd = -1; int cur_inst = 0; char line[4096]; @@ -218,6 +217,8 @@ static int process_file(const char *prefix, const char *path, const char *filena } cleanup: + if (tmp_fd != -1) + close(tmp_fd); if (fp != NULL) fclose(fp); if (whole_path != NULL) diff --git a/src/OVAL/probes/independent/yamlfilecontent_probe.c b/src/OVAL/probes/independent/yamlfilecontent_probe.c index 5a8c244deb..2cbcbf483e 100644 --- a/src/OVAL/probes/independent/yamlfilecontent_probe.c +++ b/src/OVAL/probes/independent/yamlfilecontent_probe.c @@ -26,6 +26,10 @@ #include #include +#include +#include +#include +#include #include #include @@ -390,7 +394,8 @@ static int process_yaml(yaml_parser_t *parser, const char *yamlpath, SEXP_t *ite static int process_yaml_file(const char *prefix, const char *path, const char *filename, const char *yamlpath, probe_ctx *ctx) { - int ret = 0; + int ret = 0, tmp_fd = -1; + FILE *yaml_file = NULL; char *filepath = oscap_path_join(path, filename); if (probe_path_is_blocked(filepath, ctx->blocked_paths)) { @@ -402,12 +407,25 @@ static int process_yaml_file(const char *prefix, const char *path, const char *f yaml_parser_initialize(&parser); char *filepath_with_prefix = oscap_path_join(prefix, filepath); + struct stat st; - FILE *yaml_file = fopen(filepath_with_prefix, "r"); + tmp_fd = open(filepath_with_prefix, O_RDONLY | O_NONBLOCK); + if (tmp_fd == -1) { + if (errno != ENOENT && errno != EACCES && errno != ENOTDIR + && errno != ENAMETOOLONG && errno != ELOOP) + result_error("Unable to open file '%s': %s", filepath_with_prefix, strerror(errno)); + goto cleanup; + } + if (fstat(tmp_fd, &st) == -1 + || !S_ISREG(st.st_mode) + || probe_fd_path_is_blocked(tmp_fd, ctx->blocked_paths)) + goto cleanup; + yaml_file = fdopen(tmp_fd, "r"); if (yaml_file == NULL) { result_error("Unable to open file '%s': %s", filepath_with_prefix, strerror(errno)); goto cleanup; } + tmp_fd = -1; yaml_parser_set_input_file(&parser, yaml_file); @@ -429,6 +447,8 @@ static int process_yaml_file(const char *prefix, const char *path, const char *f } cleanup: + if (tmp_fd != -1) + close(tmp_fd); if (yaml_file != NULL) fclose(yaml_file); yaml_parser_delete(&parser); diff --git a/src/OVAL/probes/probe-api.c b/src/OVAL/probes/probe-api.c index d5f0ebeb28..72f20d7998 100644 --- a/src/OVAL/probes/probe-api.c +++ b/src/OVAL/probes/probe-api.c @@ -45,6 +45,8 @@ #include /* inet_pton() in probe_ent_from_cstr() */ #include #include +#include +#include #endif #include "debug_priv.h" @@ -1810,4 +1812,22 @@ bool probe_path_is_blocked(const char *path, struct oscap_list *blocked_paths) return res; } +bool probe_fd_path_is_blocked(int fd, struct oscap_list *blocked_paths) +{ +#if defined(__linux__) + char proc_path[64]; + char resolved[PATH_MAX]; + snprintf(proc_path, sizeof(proc_path), "/proc/self/fd/%d", fd); + ssize_t len = readlink(proc_path, resolved, sizeof(resolved) - 1); + if (len == -1) + return false; + resolved[len] = '\0'; + return probe_path_is_blocked(resolved, blocked_paths); +#else + (void)fd; + (void)blocked_paths; + return false; +#endif +} + /// @} diff --git a/src/OVAL/probes/public/probe-api.h b/src/OVAL/probes/public/probe-api.h index fbce8d27d2..ffc6add9d6 100644 --- a/src/OVAL/probes/public/probe-api.h +++ b/src/OVAL/probes/public/probe-api.h @@ -546,4 +546,12 @@ OSCAP_API SEXP_t *probe_obj_getmask(SEXP_t *obj); */ OSCAP_API bool probe_path_is_blocked(const char *path, struct oscap_list *blocked_paths); +/** + * Check if the real path of an open file descriptor matches any blocked path. + * Resolves the fd target via /proc/self/fd on Linux; returns false on other platforms. + * @param fd open file descriptor + * @param blocked_paths list of blocked paths + */ +OSCAP_API bool probe_fd_path_is_blocked(int fd, struct oscap_list *blocked_paths); + /// @} From a1c7d521d648e5c440f834ada92004010d195cbf Mon Sep 17 00:00:00 2001 From: Arden97 Date: Thu, 27 Aug 2026 14:19:04 +0200 Subject: [PATCH 2/3] adding prefix stripping to probe_fd_path_is_blocked --- .../probes/independent/textfilecontent54_probe.c | 2 +- .../probes/independent/textfilecontent_probe.c | 2 +- .../probes/independent/yamlfilecontent_probe.c | 2 +- src/OVAL/probes/probe-api.c | 15 +++++++++++++-- src/OVAL/probes/public/probe-api.h | 3 ++- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/OVAL/probes/independent/textfilecontent54_probe.c b/src/OVAL/probes/independent/textfilecontent54_probe.c index 05fb8ba3a0..8b31a029c5 100644 --- a/src/OVAL/probes/independent/textfilecontent54_probe.c +++ b/src/OVAL/probes/independent/textfilecontent54_probe.c @@ -178,7 +178,7 @@ static int process_file(const char *prefix, const char *path, const char *file, */ if (fstat(fd, &st) == -1 || !S_ISREG(st.st_mode) - || probe_fd_path_is_blocked(fd, blocked_paths)) + || probe_fd_path_is_blocked(fd, prefix, blocked_paths)) goto cleanup; do { diff --git a/src/OVAL/probes/independent/textfilecontent_probe.c b/src/OVAL/probes/independent/textfilecontent_probe.c index f8efba8410..b769d1bb88 100644 --- a/src/OVAL/probes/independent/textfilecontent_probe.c +++ b/src/OVAL/probes/independent/textfilecontent_probe.c @@ -185,7 +185,7 @@ static int process_file(const char *prefix, const char *path, const char *filena } if (fstat(tmp_fd, &st) == -1 || !S_ISREG(st.st_mode) - || probe_fd_path_is_blocked(tmp_fd, blocked_paths)) + || probe_fd_path_is_blocked(tmp_fd, prefix, blocked_paths)) goto cleanup; fp = fdopen(tmp_fd, "rb"); if (fp == NULL) { diff --git a/src/OVAL/probes/independent/yamlfilecontent_probe.c b/src/OVAL/probes/independent/yamlfilecontent_probe.c index 2cbcbf483e..aee000d523 100644 --- a/src/OVAL/probes/independent/yamlfilecontent_probe.c +++ b/src/OVAL/probes/independent/yamlfilecontent_probe.c @@ -418,7 +418,7 @@ static int process_yaml_file(const char *prefix, const char *path, const char *f } if (fstat(tmp_fd, &st) == -1 || !S_ISREG(st.st_mode) - || probe_fd_path_is_blocked(tmp_fd, ctx->blocked_paths)) + || probe_fd_path_is_blocked(tmp_fd, prefix, ctx->blocked_paths)) goto cleanup; yaml_file = fdopen(tmp_fd, "r"); if (yaml_file == NULL) { diff --git a/src/OVAL/probes/probe-api.c b/src/OVAL/probes/probe-api.c index 72f20d7998..1566d6bc3b 100644 --- a/src/OVAL/probes/probe-api.c +++ b/src/OVAL/probes/probe-api.c @@ -1812,19 +1812,30 @@ bool probe_path_is_blocked(const char *path, struct oscap_list *blocked_paths) return res; } -bool probe_fd_path_is_blocked(int fd, struct oscap_list *blocked_paths) +bool probe_fd_path_is_blocked(int fd, const char *prefix, struct oscap_list *blocked_paths) { #if defined(__linux__) char proc_path[64]; char resolved[PATH_MAX]; + const char *check_path; + snprintf(proc_path, sizeof(proc_path), "/proc/self/fd/%d", fd); ssize_t len = readlink(proc_path, resolved, sizeof(resolved) - 1); if (len == -1) return false; resolved[len] = '\0'; - return probe_path_is_blocked(resolved, blocked_paths); + // blocked_paths are unprefixed, so we have strip prefix from check_path + check_path = resolved; + if (prefix && *prefix) { + size_t plen = strlen(prefix); + if (strncmp(resolved, prefix, plen) == 0) + check_path = resolved + plen; + } + + return probe_path_is_blocked(check_path, blocked_paths); #else (void)fd; + (void)prefix; (void)blocked_paths; return false; #endif diff --git a/src/OVAL/probes/public/probe-api.h b/src/OVAL/probes/public/probe-api.h index ffc6add9d6..ec47813b9e 100644 --- a/src/OVAL/probes/public/probe-api.h +++ b/src/OVAL/probes/public/probe-api.h @@ -550,8 +550,9 @@ OSCAP_API bool probe_path_is_blocked(const char *path, struct oscap_list *blocke * Check if the real path of an open file descriptor matches any blocked path. * Resolves the fd target via /proc/self/fd on Linux; returns false on other platforms. * @param fd open file descriptor + * @param prefix OSCAP_PROBE_ROOT prefix to strip from resolved path, or NULL * @param blocked_paths list of blocked paths */ -OSCAP_API bool probe_fd_path_is_blocked(int fd, struct oscap_list *blocked_paths); +OSCAP_API bool probe_fd_path_is_blocked(int fd, const char *prefix, struct oscap_list *blocked_paths); /// @} From 30db6edbf7b7c45acb8f94b01565c9285da7afc1 Mon Sep 17 00:00:00 2001 From: Arden97 Date: Mon, 31 Aug 2026 11:32:32 +0200 Subject: [PATCH 3/3] adding test for probing ignored paths through symlnks --- tests/probes/textfilecontent54/test_symlinks.sh | 6 ++++++ tests/probes/textfilecontent54/test_symlinks.xml.tpl | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/tests/probes/textfilecontent54/test_symlinks.sh b/tests/probes/textfilecontent54/test_symlinks.sh index 79135711a6..a9bf18cb74 100755 --- a/tests/probes/textfilecontent54/test_symlinks.sh +++ b/tests/probes/textfilecontent54/test_symlinks.sh @@ -15,6 +15,10 @@ sed "s@%PATH%@${tmpdir}@" $tpl > $input ln -s /etc/hosts ${tmpdir}/sl1 ln -s /nonexistent ${tmpdir}/sl2 ln -s ${tmpdir} ${tmpdir}/sl3 +mkdir -p ${tmpdir}/blocked +echo "blocked_content" > ${tmpdir}/blocked/secret +ln -s ${tmpdir}/blocked/secret ${tmpdir}/sl4 +export OSCAP_PROBE_IGNORE_PATHS="${tmpdir}/blocked" echo "Evaluating content." $OSCAP oval eval --results $result $input || [ $? == 2 ] @@ -25,10 +29,12 @@ echo "Testing syschar values." [ "$($XPATH $result 'string(/oval_results/results/system/tests/test[@test_id="oval:x:tst:2"]/@result)')" == "false" ] [ "$($XPATH $result 'string(/oval_results/results/system/tests/test[@test_id="oval:x:tst:3"]/@result)')" == "false" ] [ "$($XPATH $result 'string(/oval_results/results/system/tests/test[@test_id="oval:x:tst:4"]/@result)')" == "true" ] +[ "$($XPATH $result 'string(/oval_results/results/system/tests/test[@test_id="oval:x:tst:5"]/@result)')" == "false" ] echo "Testing syschar values." [ "$($XPATH $result 'string(/oval_results/results/system/oval_system_characteristics/collected_objects/object[@id="oval:x:obj:1"]/@flag)')" == "complete" ] [ "$($XPATH $result 'string(/oval_results/results/system/oval_system_characteristics/collected_objects/object[@id="oval:x:obj:2"]/@flag)')" == "does not exist" ] [ "$($XPATH $result 'string(/oval_results/results/system/oval_system_characteristics/collected_objects/object[@id="oval:x:obj:3"]/@flag)')" == "does not exist" ] [ "$($XPATH $result 'string(/oval_results/results/system/oval_system_characteristics/collected_objects/object[@id="oval:x:obj:4"]/@flag)')" == "complete" ] +[ "$($XPATH $result 'string(/oval_results/results/system/oval_system_characteristics/collected_objects/object[@id="oval:x:obj:5"]/@flag)')" == "does not exist" ] rm -rf $tmpdir diff --git a/tests/probes/textfilecontent54/test_symlinks.xml.tpl b/tests/probes/textfilecontent54/test_symlinks.xml.tpl index f41ce9d278..6309cecd7f 100644 --- a/tests/probes/textfilecontent54/test_symlinks.xml.tpl +++ b/tests/probes/textfilecontent54/test_symlinks.xml.tpl @@ -19,6 +19,7 @@ + @@ -36,6 +37,9 @@ + + + @@ -63,5 +67,12 @@ .* 1 + + + %PATH% + sl4 + .* + 1 +