mirror of
https://git.savannah.gnu.org/git/coreutils.git
synced 2026-10-24 02:08:57 +03:00
chcon,chgrp,chmod,chown,du,ls: ignore missing files while traversing
* src/fts-missing.h: Add helper routines to determine if an FTS entry is skippable. I.e., that is traversed and not specified on the command line. * src/chcon.c (process_file): Setup to ignore skippable files. (change_file_context): Ignore missing files if appropriate. * src/chmod.c (process_file): Setup to ignore skippable files. Ignore missing files if appropriate. (mode_changed): Ignore missing files if appropriate. * src/chown-core.c (change_file_owner): Setup to ignore skippable files. Ignore missing files if appropriate. * src/du.c (process_file): Likewise. * src/ls.c (print_dir): Ignore missing files if appropriate. (get_link_name): Likewise. (gobble_file): Likewise. Also handle the dangling symlink case. * src/system.h (ignorable_traversal_errno): Match ENOENT for missing files. * tests/chcon/traversal-missing.sh: A new test. * tests/misc/traversal-missing.sh: Likewise. * tests/local.mk: Reference the new tests. * NEWS: Mention the bug fix. Fixes https://bugs.gnu.org/81444
This commit is contained in:
4
NEWS
4
NEWS
@@ -4,6 +4,10 @@ GNU coreutils NEWS -*- outline -*-
|
||||
|
||||
** Bug fixes
|
||||
|
||||
'chcon', 'chgrp', 'chmod', 'chown', 'du', 'ls' which traverse hierarchies with
|
||||
-R, no longer fail merely because files may be being removed in parallel.
|
||||
[This bug was present in "the beginning".]
|
||||
|
||||
'comm - -' no longer closes standard input twice. Previously it would
|
||||
mistakenly exit with a nonzero status.
|
||||
[This bug was present in "the beginning".]
|
||||
|
||||
20
src/chcon.c
20
src/chcon.c
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "system.h"
|
||||
#include "dev-ino.h"
|
||||
#include "fts-missing.h"
|
||||
#include "ignore-value.h"
|
||||
#include "quote.h"
|
||||
#include "root-dev-ino.h"
|
||||
@@ -137,7 +138,7 @@ compute_context_from_mask (char const *context, context_t *ret)
|
||||
Return 0 if successful, 1 if errors occurred. */
|
||||
|
||||
static int
|
||||
change_file_context (int fd, char const *file)
|
||||
change_file_context (int fd, char const *file, bool ignore_missing)
|
||||
{
|
||||
char *file_context = NULL;
|
||||
context_t context IF_LINT (= NULL);
|
||||
@@ -152,6 +153,8 @@ change_file_context (int fd, char const *file)
|
||||
|
||||
if (status < 0 && errno != ENODATA)
|
||||
{
|
||||
if (ignore_missing && ignorable_traversal_errno (errno))
|
||||
return 0;
|
||||
error (0, errno, _("failed to get security context of %s"),
|
||||
quoteaf (file));
|
||||
return 1;
|
||||
@@ -188,9 +191,12 @@ change_file_context (int fd, char const *file)
|
||||
|
||||
if (fail)
|
||||
{
|
||||
errors = 1;
|
||||
error (0, errno, _("failed to change context of %s to %s"),
|
||||
quoteaf_n (0, file), quote_n (1, context_string));
|
||||
if (! (ignore_missing && ignorable_traversal_errno (errno)))
|
||||
{
|
||||
errors = 1;
|
||||
error (0, errno, _("failed to change context of %s to %s"),
|
||||
quoteaf_n (0, file), quote_n (1, context_string));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +220,10 @@ process_file (FTS *fts, FTSENT *ent)
|
||||
char const *file = ent->fts_accpath;
|
||||
const struct stat *file_stats = ent->fts_statp;
|
||||
bool ok = true;
|
||||
bool ignore_missing = ignore_missing_fts_entry (ent);
|
||||
|
||||
if (ignore_missing && ignorable_fts_error (ent))
|
||||
return true;
|
||||
|
||||
switch (ent->fts_info)
|
||||
{
|
||||
@@ -295,7 +305,7 @@ process_file (FTS *fts, FTSENT *ent)
|
||||
printf (_("changing security context of %s\n"),
|
||||
quoteaf (file_full_name));
|
||||
|
||||
if (change_file_context (fts->fts_cwd_fd, file) != 0)
|
||||
if (change_file_context (fts->fts_cwd_fd, file, ignore_missing) != 0)
|
||||
ok = false;
|
||||
}
|
||||
|
||||
|
||||
29
src/chmod.c
29
src/chmod.c
@@ -25,6 +25,7 @@
|
||||
#include "assure.h"
|
||||
#include "dev-ino.h"
|
||||
#include "filemode.h"
|
||||
#include "fts-missing.h"
|
||||
#include "ignore-value.h"
|
||||
#include "modechange.h"
|
||||
#include "quote.h"
|
||||
@@ -125,8 +126,10 @@ static struct option const long_options[] =
|
||||
|
||||
static bool
|
||||
mode_changed (int dir_fd, char const *file, char const *file_full_name,
|
||||
mode_t old_mode, mode_t new_mode)
|
||||
mode_t old_mode, mode_t new_mode, bool ignore_missing,
|
||||
bool *file_missing)
|
||||
{
|
||||
*file_missing = false;
|
||||
if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
|
||||
{
|
||||
/* The new mode contains unusual bits that the call to chmod may
|
||||
@@ -136,7 +139,9 @@ mode_changed (int dir_fd, char const *file, char const *file_full_name,
|
||||
|
||||
if (fstatat (dir_fd, file, &new_stats, 0) != 0)
|
||||
{
|
||||
if (! force_silent)
|
||||
*file_missing = (ignore_missing
|
||||
&& ignorable_traversal_errno (errno));
|
||||
if (! *file_missing && ! force_silent)
|
||||
error (0, errno, _("getting new attributes of %s"),
|
||||
quoteaf (file_full_name));
|
||||
return false;
|
||||
@@ -215,6 +220,10 @@ process_file (FTS *fts, FTSENT *ent)
|
||||
struct change_status ch = {0};
|
||||
ch.status = CH_NO_STAT;
|
||||
struct stat stat_buf;
|
||||
bool ignore_missing = ignore_missing_fts_entry (ent);
|
||||
|
||||
if (ignore_missing && ignorable_fts_error (ent))
|
||||
return true;
|
||||
|
||||
switch (ent->fts_info)
|
||||
{
|
||||
@@ -267,6 +276,9 @@ process_file (FTS *fts, FTSENT *ent)
|
||||
{
|
||||
if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0)
|
||||
{
|
||||
if (ignore_missing
|
||||
&& ignorable_traversal_errno (errno))
|
||||
return true;
|
||||
if (! force_silent)
|
||||
error (0, errno, _("cannot dereference %s"),
|
||||
quoteaf (file_full_name));
|
||||
@@ -314,6 +326,9 @@ process_file (FTS *fts, FTSENT *ent)
|
||||
ch.status = CH_SUCCEEDED;
|
||||
else
|
||||
{
|
||||
if (ignore_missing
|
||||
&& ignorable_traversal_errno (errno))
|
||||
return true;
|
||||
if (! is_ENOTSUP (errno))
|
||||
{
|
||||
if (! force_silent)
|
||||
@@ -328,10 +343,16 @@ process_file (FTS *fts, FTSENT *ent)
|
||||
|
||||
if (verbosity != V_off)
|
||||
{
|
||||
bool file_missing;
|
||||
if (ch.status == CH_SUCCEEDED
|
||||
&& !mode_changed (fts->fts_cwd_fd, file, file_full_name,
|
||||
ch.old_mode, ch.new_mode))
|
||||
ch.status = CH_NO_CHANGE_REQUESTED;
|
||||
ch.old_mode, ch.new_mode, ignore_missing,
|
||||
&file_missing))
|
||||
{
|
||||
if (file_missing)
|
||||
return true;
|
||||
ch.status = CH_NO_CHANGE_REQUESTED;
|
||||
}
|
||||
|
||||
if (ch.status == CH_SUCCEEDED || verbosity == V_high)
|
||||
describe_change (file_full_name, &ch);
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "system.h"
|
||||
#include "assure.h"
|
||||
#include "chown-core.h"
|
||||
#include "fts-missing.h"
|
||||
#include "ignore-value.h"
|
||||
#include "root-dev-ino.h"
|
||||
#include "xfts.h"
|
||||
@@ -284,6 +285,10 @@ change_file_owner (FTS *fts, FTSENT *ent,
|
||||
char const *file_full_name = ent->fts_path;
|
||||
char const *file = ent->fts_accpath;
|
||||
bool ok = true;
|
||||
bool ignore_missing = ignore_missing_fts_entry (ent);
|
||||
|
||||
if (ignore_missing && ignorable_fts_error (ent))
|
||||
return true;
|
||||
|
||||
switch (ent->fts_info)
|
||||
{
|
||||
@@ -381,6 +386,9 @@ change_file_owner (FTS *fts, FTSENT *ent,
|
||||
{
|
||||
if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0)
|
||||
{
|
||||
if (ignore_missing
|
||||
&& ignorable_traversal_errno (errno))
|
||||
return true;
|
||||
if (! chopt->force_silent)
|
||||
error (0, errno, _("cannot dereference %s"),
|
||||
quoteaf (file_full_name));
|
||||
@@ -469,11 +477,17 @@ change_file_owner (FTS *fts, FTSENT *ent,
|
||||
by some other user and operating on files in a directory
|
||||
where M has write access. */
|
||||
|
||||
if (do_chown && !ok && ! chopt->force_silent)
|
||||
error (0, errno, (uid != (uid_t) -1
|
||||
? _("changing ownership of %s")
|
||||
: _("changing group of %s")),
|
||||
quoteaf (file_full_name));
|
||||
if (do_chown && !ok)
|
||||
{
|
||||
if (ignore_missing
|
||||
&& ignorable_traversal_errno (errno))
|
||||
return true;
|
||||
if (! chopt->force_silent)
|
||||
error (0, errno, (uid != (uid_t) -1
|
||||
? _("changing ownership of %s")
|
||||
: _("changing group of %s")),
|
||||
quoteaf (file_full_name));
|
||||
}
|
||||
}
|
||||
|
||||
if (chopt->verbosity != V_off)
|
||||
|
||||
8
src/du.c
8
src/du.c
@@ -32,6 +32,7 @@
|
||||
#include "assure.h"
|
||||
#include "di-set.h"
|
||||
#include "exclude.h"
|
||||
#include "fts-missing.h"
|
||||
#include "human.h"
|
||||
#include "mountlist.h"
|
||||
#include "quote.h"
|
||||
@@ -554,6 +555,10 @@ process_file (FTS *fts, FTSENT *ent)
|
||||
char const *file = ent->fts_path;
|
||||
const struct stat *sb = ent->fts_statp;
|
||||
int info = ent->fts_info;
|
||||
bool ignore_missing = ignore_missing_fts_entry (ent);
|
||||
|
||||
if (ignore_missing && ignorable_fts_error (ent))
|
||||
return true;
|
||||
|
||||
if (info == FTS_DNR)
|
||||
{
|
||||
@@ -574,6 +579,9 @@ process_file (FTS *fts, FTSENT *ent)
|
||||
MAYBE_UNUSED FTSENT const *e = fts_read (fts);
|
||||
affirm (e == ent);
|
||||
info = ent->fts_info;
|
||||
|
||||
if (ignore_missing && ignorable_fts_error (ent))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (info == FTS_NS || info == FTS_SLNONE)
|
||||
|
||||
39
src/fts-missing.h
Normal file
39
src/fts-missing.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/* fts-missing.h -- helpers for files missing during an FTS traversal.
|
||||
|
||||
Copyright (C) 2026 Free Software Foundation, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
|
||||
#ifndef FTS_MISSING_H
|
||||
# define FTS_MISSING_H
|
||||
|
||||
# include "fts_.h"
|
||||
|
||||
static inline bool
|
||||
ignore_missing_fts_entry (FTSENT const *ent)
|
||||
{
|
||||
return (FTS_ROOTLEVEL < ent->fts_level
|
||||
&& ent->fts_info != FTS_SL
|
||||
&& ent->fts_info != FTS_SLNONE);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
ignorable_fts_error (FTSENT const *ent)
|
||||
{
|
||||
return (ignorable_traversal_errno (ent->fts_errno)
|
||||
&& (ent->fts_info == FTS_NS || ent->fts_info == FTS_ERR
|
||||
|| ent->fts_info == FTS_DNR));
|
||||
}
|
||||
|
||||
#endif /* FTS_MISSING_H */
|
||||
@@ -50,6 +50,7 @@ noinst_HEADERS = \
|
||||
src/find-mount-point.h \
|
||||
src/fs.h \
|
||||
src/fs-is-local.h \
|
||||
src/fts-missing.h \
|
||||
src/group-list.h \
|
||||
src/ioblksize.h \
|
||||
src/iopoll.h \
|
||||
|
||||
62
src/ls.c
62
src/ls.c
@@ -279,7 +279,7 @@ static void attach (char *dest, char const *dirname, char const *name);
|
||||
static void clear_files (void);
|
||||
static void extract_dirs_from_files (char const *dirname,
|
||||
bool command_line_arg);
|
||||
static void get_link_name (char const *filename, struct fileinfo *f,
|
||||
static bool get_link_name (char const *filename, struct fileinfo *f,
|
||||
bool command_line_arg);
|
||||
static void indent (size_t from, size_t to);
|
||||
static idx_t calculate_columns (bool by_columns);
|
||||
@@ -2961,7 +2961,8 @@ print_dir (char const *name, char const *realname, bool command_line_arg)
|
||||
dirp = opendir (name);
|
||||
if (!dirp)
|
||||
{
|
||||
file_failure (command_line_arg, _("cannot open directory %s"), name);
|
||||
if (command_line_arg || ! ignorable_traversal_errno (errno))
|
||||
file_failure (command_line_arg, _("cannot open directory %s"), name);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2975,8 +2976,9 @@ print_dir (char const *name, char const *realname, bool command_line_arg)
|
||||
? fstat_for_ino (fd, &dir_stat)
|
||||
: stat_for_ino (name, &dir_stat)) < 0)
|
||||
{
|
||||
file_failure (command_line_arg,
|
||||
_("cannot determine device and inode of %s"), name);
|
||||
if (! ignorable_traversal_errno (errno))
|
||||
file_failure (command_line_arg,
|
||||
_("cannot determine device and inode of %s"), name);
|
||||
closedir (dirp);
|
||||
return;
|
||||
}
|
||||
@@ -3008,7 +3010,8 @@ print_dir (char const *name, char const *realname, bool command_line_arg)
|
||||
if (print_hyperlink)
|
||||
{
|
||||
absolute_name = canonicalize_filename_mode (name, CAN_MISSING);
|
||||
if (! absolute_name)
|
||||
if (! absolute_name
|
||||
&& ! ignorable_traversal_errno (errno))
|
||||
file_failure (command_line_arg,
|
||||
_("error canonicalizing %s"), name);
|
||||
}
|
||||
@@ -3064,12 +3067,9 @@ print_dir (char const *name, char const *realname, bool command_line_arg)
|
||||
int err = errno;
|
||||
if (err == 0)
|
||||
break;
|
||||
/* Some readdir()s do not absorb ENOENT (dir deleted but open).
|
||||
This bug was fixed in glibc 2.3 (2002). */
|
||||
#if ! (2 < __GLIBC__ + (3 <= __GLIBC_MINOR__))
|
||||
if (err == ENOENT)
|
||||
/* Ignore errors indicating that the directory was removed. */
|
||||
if (ignorable_traversal_errno (err))
|
||||
break;
|
||||
#endif
|
||||
file_failure (command_line_arg, _("reading directory %s"), name);
|
||||
if (err != EOVERFLOW)
|
||||
break;
|
||||
@@ -3391,7 +3391,9 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
|
||||
{
|
||||
f->absolute_name = canonicalize_filename_mode (full_name,
|
||||
CAN_MISSING);
|
||||
if (! f->absolute_name)
|
||||
if (! f->absolute_name
|
||||
&& (command_line_arg
|
||||
|| ! ignorable_traversal_errno (errno)))
|
||||
file_failure (command_line_arg,
|
||||
_("error canonicalizing %s"), full_name);
|
||||
}
|
||||
@@ -3439,6 +3441,22 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
|
||||
|
||||
if (err != 0)
|
||||
{
|
||||
if (! command_line_arg && ignorable_traversal_errno (errno))
|
||||
{
|
||||
int stat_errno = errno;
|
||||
struct stat linkstat;
|
||||
bool dangling_link = (do_deref
|
||||
&& (type == symbolic_link
|
||||
|| type == unknown)
|
||||
&& do_lstat (full_name, &linkstat) == 0);
|
||||
errno = stat_errno;
|
||||
if (! dangling_link)
|
||||
{
|
||||
free_ent (f);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Failure to stat a command line argument leads to
|
||||
an exit status of 2. For other files, stat failure
|
||||
provokes an exit status of 1. */
|
||||
@@ -3483,7 +3501,8 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
|
||||
Also if a file is removed while we're reading ACL info,
|
||||
ACL_T_UNKNOWN is sufficient indication for that edge case. */
|
||||
bool cannot_access_acl = n < 0
|
||||
&& (errno == EACCES || errno == ENOENT);
|
||||
&& (errno == EACCES
|
||||
|| ignorable_traversal_errno (errno));
|
||||
|
||||
f->acl_type = (!have_scontext && !have_acl
|
||||
? (cannot_access_acl ? ACL_T_UNKNOWN : ACL_T_NONE)
|
||||
@@ -3501,6 +3520,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
|
||||
isn't on the right type of file system. I.e., a getfilecon
|
||||
failure isn't in the same class as a stat failure. */
|
||||
if (print_scontext && ai.scontext_err
|
||||
&& ! ignorable_traversal_errno (ai.scontext_err)
|
||||
&& (! (is_ENOTSUP (ai.scontext_err)
|
||||
|| ai.scontext_err == ENODATA)))
|
||||
error (0, ai.scontext_err, "%s", quotef (full_name));
|
||||
@@ -3522,7 +3542,11 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
|
||||
{
|
||||
struct stat linkstats;
|
||||
|
||||
get_link_name (full_name, f, command_line_arg);
|
||||
if (! get_link_name (full_name, f, command_line_arg))
|
||||
{
|
||||
free_ent (f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Use the slower quoting path for this entry, though
|
||||
don't update CWD_SOME_QUOTED since alignment not affected. */
|
||||
@@ -3648,13 +3672,19 @@ is_linked_directory (const struct fileinfo *f)
|
||||
into the LINKNAME field of 'f'. COMMAND_LINE_ARG indicates whether
|
||||
FILENAME is a command-line argument. */
|
||||
|
||||
static void
|
||||
static bool
|
||||
get_link_name (char const *filename, struct fileinfo *f, bool command_line_arg)
|
||||
{
|
||||
f->linkname = areadlink_with_size (filename, f->stat.st_size);
|
||||
if (f->linkname == NULL)
|
||||
file_failure (command_line_arg, _("cannot read symbolic link %s"),
|
||||
filename);
|
||||
{
|
||||
if (! command_line_arg
|
||||
&& ignorable_traversal_errno (errno))
|
||||
return false;
|
||||
file_failure (command_line_arg, _("cannot read symbolic link %s"),
|
||||
filename);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Return true if the last component of NAME is '.' or '..'
|
||||
|
||||
@@ -83,6 +83,11 @@
|
||||
# define ENODATA (-1)
|
||||
#endif
|
||||
|
||||
/* Return true if ERRNUM indicates that a file disappeared during a
|
||||
directory traversal. */
|
||||
static inline bool
|
||||
ignorable_traversal_errno (int errnum) { return errnum == ENOENT; }
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "version.h"
|
||||
|
||||
|
||||
48
tests/chcon/traversal-missing.sh
Executable file
48
tests/chcon/traversal-missing.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/bin/sh
|
||||
# Test ignoring traversal races using strace fault injection.
|
||||
|
||||
# Copyright (C) 2026 Free Software Foundation, Inc.
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
|
||||
print_ver_ chcon
|
||||
require_strace_ getxattr
|
||||
|
||||
mkdir d || framework_failure_
|
||||
touch d/foo || framework_failure_
|
||||
xattrs=getxattr,lgetxattr,setxattr,lsetxattr
|
||||
|
||||
(cd d && chcon -R -t user_tmp_t . > /dev/null 2>&1) \
|
||||
|| skip_ 'chcon does not work on the test file system'
|
||||
|
||||
# Skip if strace does not support path-filtered syscall injection.
|
||||
strace --quiet=all -o /dev/null -P /proc/self/fd/4/foo \
|
||||
-e inject=$xattrs:error=ENOENT true 2> /dev/null \
|
||||
|| skip_ 'strace does not support the required options and syscalls'
|
||||
|
||||
for errnum in ENOENT; do
|
||||
rm -f err trace
|
||||
(cd d && strace --quiet=all -o ../trace -P /proc/self/fd/4/foo \
|
||||
-e inject=$xattrs:error=$errnum chcon -R -t user_tmp_t . \
|
||||
> /dev/null 2> ../err)
|
||||
status=$?
|
||||
|
||||
grep ' (INJECTED)' trace > /dev/null 2>&1 \
|
||||
|| skip_ 'strace did not inject the requested failure'
|
||||
test $status -eq 0 || fail=1
|
||||
test ! -s err || { cat err; fail=1; }
|
||||
done
|
||||
|
||||
Exit $fail
|
||||
@@ -185,6 +185,7 @@ all_tests = \
|
||||
tests/misc/io-errors.sh \
|
||||
tests/misc/read-errors.sh \
|
||||
tests/misc/responsive.sh \
|
||||
tests/misc/traversal-missing.sh \
|
||||
tests/misc/warning-errors.sh \
|
||||
tests/misc/write-errors.sh \
|
||||
tests/tail/basic-seek.sh \
|
||||
@@ -297,6 +298,7 @@ all_tests = \
|
||||
tests/pwd/argument.sh \
|
||||
tests/pwd/pwd-option.sh \
|
||||
tests/chcon/chcon-fail.sh \
|
||||
tests/chcon/traversal-missing.sh \
|
||||
tests/misc/coreutils.sh \
|
||||
tests/cut/cut.pl \
|
||||
tests/cut/mb-non-utf8.sh \
|
||||
|
||||
74
tests/misc/traversal-missing.sh
Executable file
74
tests/misc/traversal-missing.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/bin/sh
|
||||
# Test ignoring traversal races using strace fault injection.
|
||||
|
||||
# Copyright (C) 2026 Free Software Foundation, Inc.
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
|
||||
print_ver_ ls chmod chown chgrp du
|
||||
require_strace_ stat
|
||||
|
||||
mkdir d || framework_failure_
|
||||
touch d/foo || framework_failure_
|
||||
uid=$(id -u) || framework_failure_
|
||||
gid=$(id -g) || framework_failure_
|
||||
|
||||
stats='stat'
|
||||
# List other _file name_ stat functions to increase coverage.
|
||||
other_stats='statx lstat stat64 lstat64 newfstatat fstatat64'
|
||||
for stat in $other_stats; do
|
||||
strace -qe "$stat" true > /dev/null 2>&1 &&
|
||||
stats="$stats,$stat"
|
||||
done
|
||||
|
||||
chmods=chmod,fchmodat,fchmodat2
|
||||
chowns=chown,lchown,fchownat
|
||||
|
||||
# Skip if strace does not support path-filtered syscall injection.
|
||||
strace --quiet=all -o /dev/null -P foo \
|
||||
-e inject=$stats:error=ENOENT true 2> /dev/null \
|
||||
|| skip_ 'strace does not support the required options and syscalls'
|
||||
|
||||
run_injected_ ()
|
||||
{
|
||||
path=$1
|
||||
syscalls=$2
|
||||
shift 2
|
||||
rm -f err trace
|
||||
(cd d && strace --quiet=all -o ../trace -P "$path" \
|
||||
-e inject="$syscalls:error=$errnum" "$@" > /dev/null 2> ../err)
|
||||
status=$?
|
||||
|
||||
grep ' (INJECTED)' trace > /dev/null 2>&1 \
|
||||
|| skip_ 'strace did not inject the requested failure'
|
||||
test $status -eq $expected_status || fail=1
|
||||
if test $expected_status -eq 0; then
|
||||
test ! -s err || { cat err; fail=1; }
|
||||
else
|
||||
test -s err || fail=1
|
||||
fi
|
||||
}
|
||||
|
||||
expected_status=0
|
||||
for errnum in ENOENT EIO; do
|
||||
test $errnum = EIO && expected_status=1
|
||||
run_injected_ foo "$stats" ls -l .
|
||||
run_injected_ foo "$stats" du -a .
|
||||
run_injected_ foo "$chmods" chmod -R 0700 .
|
||||
run_injected_ foo "$chowns" chown -R "$uid" .
|
||||
run_injected_ foo "$chowns" chgrp -R "$gid" .
|
||||
done
|
||||
|
||||
Exit $fail
|
||||
Reference in New Issue
Block a user