cp: Fix issues with destination directory mode.

Ensure that we are able to enter the destination directory after we
create it, even if the current umask would normally prevent it, and
that it has the expected permissions once we are done, even if we had
to tweak them to be able to enter it.

Fixes:		82fc0d09e8
Sponsored by:	Klara, Inc.
Reviewed by:	markj
Differential Revision:	https://reviews.freebsd.org/D50266
This commit is contained in:
Dag-Erling Smørgrav 2025-05-10 10:55:41 +02:00
parent be7839151c
commit 48578dcb6b
2 changed files with 52 additions and 4 deletions

View file

@ -331,10 +331,18 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
assert(to.dir < 0);
assert(root_stat == NULL);
mode = curr_stat->st_mode | S_IRWXU;
/*
* Will our umask prevent us from entering
* the directory after we create it?
*/
if (~mask & S_IRWXU)
umask(~mask & ~S_IRWXU);
if (mkdir(to.base, mode) != 0) {
warn("%s", to.base);
fts_set(ftsp, curr, FTS_SKIP);
badcp = rval = 1;
if (~mask & S_IRWXU)
umask(~mask);
continue;
}
to.dir = open(to.base, O_DIRECTORY | O_SEARCH);
@ -343,6 +351,8 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
(void)rmdir(to.base);
fts_set(ftsp, curr, FTS_SKIP);
badcp = rval = 1;
if (~mask & S_IRWXU)
umask(~mask);
continue;
}
if (fstat(to.dir, &created_root_stat) != 0) {
@ -352,9 +362,14 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
fts_set(ftsp, curr, FTS_SKIP);
to.dir = -1;
badcp = rval = 1;
if (~mask & S_IRWXU)
umask(~mask);
continue;
}
if (~mask & S_IRWXU)
umask(~mask);
root_stat = &created_root_stat;
curr->fts_number = 1;
} else {
/* entering a directory; append its name to to.path */
len = snprintf(to.end, END(to.path) - to.end, "%s%s",
@ -432,9 +447,7 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
} else if (curr->fts_number) {
const char *path = *to.path ? to.path : dot;
mode = curr_stat->st_mode;
if (((mode & (S_ISUID | S_ISGID | S_ISTXT)) ||
((mode | S_IRWXU) & mask) != (mode & mask)) &&
fchmodat(to.dir, path, mode & mask, 0) != 0) {
if (fchmodat(to.dir, path, mode & mask, 0) != 0) {
warn("chmod: %s/%s", to.base, to.path);
rval = 1;
}
@ -538,12 +551,22 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
*/
if (dne) {
mode = curr_stat->st_mode | S_IRWXU;
/*
* Will our umask prevent us from entering
* the directory after we create it?
*/
if (~mask & S_IRWXU)
umask(~mask & ~S_IRWXU);
if (mkdirat(to.dir, to.path, mode) != 0) {
warn("%s/%s", to.base, to.path);
fts_set(ftsp, curr, FTS_SKIP);
badcp = rval = 1;
if (~mask & S_IRWXU)
umask(~mask);
break;
}
if (~mask & S_IRWXU)
umask(~mask);
} else if (!S_ISDIR(to_stat.st_mode)) {
warnc(ENOTDIR, "%s/%s", to.base, to.path);
fts_set(ftsp, curr, FTS_SKIP);
@ -554,8 +577,10 @@ copy(char *argv[], enum op type, int fts_options, struct stat *root_stat)
* Arrange to correct directory attributes later
* (in the post-order phase) if this is a new
* directory, or if the -p flag is in effect.
* Note that fts_number may already be set if this
* is the newly created destination directory.
*/
curr->fts_number = pflag || dne;
curr->fts_number |= pflag || dne;
break;
case S_IFBLK:
case S_IFCHR:

View file

@ -557,6 +557,28 @@ to_link_outside_body()
cp -r dir dst
}
atf_test_case dstmode
dstmode_body()
{
mkdir -m 0755 dir
echo "foo" >dir/file
umask 0177
#atf_check cp -R dir dst
#begin
# atf-check stupidly refuses to work if the current umask is
# weird, instead of just dealing with the situation
cp -R dir dst >stdout 2>stderr
rc=$?
umask 022
atf_check_equal 0 $rc
atf_check cat stdout
atf_check cat stderr
#end
atf_check -o inline:"40600\n" stat -f%p dst
atf_check chmod 0750 dst
atf_check cmp dir/file dst/file
}
atf_init_test_cases()
{
atf_add_test_case basic
@ -593,4 +615,5 @@ atf_init_test_cases()
atf_add_test_case to_dirlink
atf_add_test_case to_deaddirlink
atf_add_test_case to_link_outside
atf_add_test_case dstmode
}