diff options
| author | Paul Eggert | 2020-02-23 16:19:42 -0800 |
|---|---|---|
| committer | Paul Eggert | 2020-02-23 16:45:50 -0800 |
| commit | 9d626dffc6ba62c0d7a1a5c712f576ed8684fd66 (patch) | |
| tree | 6cc8fbe8e5bc02c3bb74139710814a0400e91a8a /lib | |
| parent | c4ca8219dd6b8f06e67a0b767475b1259653b8e0 (diff) | |
| download | emacs-9d626dffc6ba62c0d7a1a5c712f576ed8684fd66.tar.gz emacs-9d626dffc6ba62c0d7a1a5c712f576ed8684fd66.zip | |
Add 'nofollow' flag to set-file-modes etc.
This avoids some race conditions (Bug#39683). E.g., if some other
program changes a file to a symlink between the time Emacs creates
the file and the time it changes the file’s permissions, using the
new flag prevents Emacs from inadvertently changing the
permissions of a victim in some completely unrelated directory.
* admin/merge-gnulib (GNULIB_MODULES): Add fchmodat.
* doc/lispref/files.texi (Testing Accessibility, Changing Files):
* doc/lispref/os.texi (File Notifications):
* etc/NEWS:
Adjust documentation accordingly.
* lib/chmodat.c, lib/fchmodat.c, lib/lchmod.c, m4/fchmodat.m4:
* m4/lchmod.m4: New files, copied from Gnulib.
* lib/gnulib.mk.in: Regenerate.
* lisp/dired-aux.el (dired-do-chmod):
* lisp/doc-view.el (doc-view-make-safe-dir):
* lisp/emacs-lisp/autoload.el (autoload--save-buffer):
* lisp/emacs-lisp/bytecomp.el (byte-compile-file):
* lisp/eshell/em-pred.el (eshell-pred-file-mode):
* lisp/files.el (backup-buffer-copy, copy-directory):
* lisp/gnus/mail-source.el (mail-source-movemail):
* lisp/gnus/mm-decode.el (mm-display-external):
* lisp/gnus/nnmail.el (nnmail-write-region):
* lisp/net/tramp-adb.el (tramp-adb-handle-file-local-copy)
(tramp-adb-handle-write-region):
* lisp/net/tramp-sh.el (tramp-do-copy-or-rename-file-directly):
* lisp/net/tramp-sudoedit.el (tramp-sudoedit-handle-write-region):
* lisp/net/tramp.el (tramp-handle-write-region)
(tramp-make-tramp-temp-file):
* lisp/server.el (server-ensure-safe-dir):
* lisp/url/url-util.el (url-make-private-file):
When getting or setting file modes, avoid following symbolic links
when the file is not supposed to be a symbolic link.
* lisp/doc-view.el (doc-view-make-safe-dir):
Omit no-longer-needed separate symlink test.
* lisp/gnus/gnus-util.el (gnus-set-file-modes):
* lisp/net/tramp.el (tramp-handle-file-modes):
* lisp/net/tramp-gvfs.el (tramp-gvfs-handle-set-file-modes):
* src/fileio.c (symlink_nofollow_flag): New function.
(Ffile_modes, Fset_file_modes):
Support an optional FLAG arg. All C callers changed.
* lisp/net/ange-ftp.el (ange-ftp-set-file-modes):
* lisp/net/tramp-adb.el (tramp-adb-handle-set-file-modes):
* lisp/net/tramp-sh.el (tramp-sh-handle-set-file-modes):
* lisp/net/tramp-smb.el (tramp-smb-handle-set-file-modes):
* lisp/net/tramp-sudoedit.el (tramp-sudoedit-handle-set-file-modes):
Accept an optional FLAG arg that is currently ignored,
and add a FIXME comment for it.
* m4/gnulib-comp.m4: Regenerate.
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/fchmodat.c | 144 | ||||
| -rw-r--r-- | lib/gnulib.mk.in | 26 | ||||
| -rw-r--r-- | lib/lchmod.c | 110 |
3 files changed, 280 insertions, 0 deletions
diff --git a/lib/fchmodat.c b/lib/fchmodat.c new file mode 100644 index 00000000000..8950168608f --- /dev/null +++ b/lib/fchmodat.c | |||
| @@ -0,0 +1,144 @@ | |||
| 1 | /* Change the protections of file relative to an open directory. | ||
| 2 | Copyright (C) 2006, 2009-2020 Free Software Foundation, Inc. | ||
| 3 | |||
| 4 | This program is free software: you can redistribute it and/or modify | ||
| 5 | it under the terms of the GNU General Public License as published by | ||
| 6 | the Free Software Foundation; either version 3 of the License, or | ||
| 7 | (at your option) any later version. | ||
| 8 | |||
| 9 | This program is distributed in the hope that it will be useful, | ||
| 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 12 | GNU General Public License for more details. | ||
| 13 | |||
| 14 | You should have received a copy of the GNU General Public License | ||
| 15 | along with this program. If not, see <https://www.gnu.org/licenses/>. */ | ||
| 16 | |||
| 17 | /* written by Jim Meyering and Paul Eggert */ | ||
| 18 | |||
| 19 | /* If the user's config.h happens to include <sys/stat.h>, let it include only | ||
| 20 | the system's <sys/stat.h> here, so that orig_fchmodat doesn't recurse to | ||
| 21 | rpl_fchmodat. */ | ||
| 22 | #define __need_system_sys_stat_h | ||
| 23 | #include <config.h> | ||
| 24 | |||
| 25 | /* Specification. */ | ||
| 26 | #include <sys/stat.h> | ||
| 27 | #undef __need_system_sys_stat_h | ||
| 28 | |||
| 29 | #if HAVE_FCHMODAT | ||
| 30 | static int | ||
| 31 | orig_fchmodat (int dir, char const *file, mode_t mode, int flags) | ||
| 32 | { | ||
| 33 | return fchmodat (dir, file, mode, flags); | ||
| 34 | } | ||
| 35 | #endif | ||
| 36 | |||
| 37 | #include <errno.h> | ||
| 38 | #include <fcntl.h> | ||
| 39 | #include <stdio.h> | ||
| 40 | #include <stdlib.h> | ||
| 41 | #include <unistd.h> | ||
| 42 | |||
| 43 | #ifdef __osf__ | ||
| 44 | /* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc | ||
| 45 | eliminates this include because of the preliminary #include <sys/stat.h> | ||
| 46 | above. */ | ||
| 47 | # include "sys/stat.h" | ||
| 48 | #else | ||
| 49 | # include <sys/stat.h> | ||
| 50 | #endif | ||
| 51 | |||
| 52 | #include <intprops.h> | ||
| 53 | |||
| 54 | /* Invoke chmod or lchmod on FILE, using mode MODE, in the directory | ||
| 55 | open on descriptor FD. If possible, do it without changing the | ||
| 56 | working directory. Otherwise, resort to using save_cwd/fchdir, | ||
| 57 | then (chmod|lchmod)/restore_cwd. If either the save_cwd or the | ||
| 58 | restore_cwd fails, then give a diagnostic and exit nonzero. | ||
| 59 | Note that an attempt to use a FLAG value of AT_SYMLINK_NOFOLLOW | ||
| 60 | on a system without lchmod support causes this function to fail. */ | ||
| 61 | |||
| 62 | #if HAVE_FCHMODAT | ||
| 63 | int | ||
| 64 | fchmodat (int dir, char const *file, mode_t mode, int flags) | ||
| 65 | { | ||
| 66 | # if NEED_FCHMODAT_NONSYMLINK_FIX | ||
| 67 | if (flags == AT_SYMLINK_NOFOLLOW) | ||
| 68 | { | ||
| 69 | struct stat st; | ||
| 70 | |||
| 71 | # if defined O_PATH && defined AT_EMPTY_PATH | ||
| 72 | /* Open a file descriptor with O_NOFOLLOW, to make sure we don't | ||
| 73 | follow symbolic links, if /proc is mounted. O_PATH is used to | ||
| 74 | avoid a failure if the file is not readable. | ||
| 75 | Cf. <https://sourceware.org/bugzilla/show_bug.cgi?id=14578> */ | ||
| 76 | int fd = openat (dir, file, O_PATH | O_NOFOLLOW | O_CLOEXEC); | ||
| 77 | if (fd < 0) | ||
| 78 | return fd; | ||
| 79 | |||
| 80 | /* Up to Linux 5.3 at least, when FILE refers to a symbolic link, the | ||
| 81 | chmod call below will change the permissions of the symbolic link | ||
| 82 | - which is undesired - and on many file systems (ext4, btrfs, jfs, | ||
| 83 | xfs, ..., but not reiserfs) fail with error EOPNOTSUPP - which is | ||
| 84 | misleading. Therefore test for a symbolic link explicitly. | ||
| 85 | Use fstatat because fstat does not work on O_PATH descriptors | ||
| 86 | before Linux 3.6. */ | ||
| 87 | if (fstatat (fd, "", &st, AT_EMPTY_PATH) != 0) | ||
| 88 | { | ||
| 89 | int stat_errno = errno; | ||
| 90 | close (fd); | ||
| 91 | errno = stat_errno; | ||
| 92 | return -1; | ||
| 93 | } | ||
| 94 | if (S_ISLNK (st.st_mode)) | ||
| 95 | { | ||
| 96 | close (fd); | ||
| 97 | errno = EOPNOTSUPP; | ||
| 98 | return -1; | ||
| 99 | } | ||
| 100 | |||
| 101 | # if defined __linux__ || defined __ANDROID__ | ||
| 102 | static char const fmt[] = "/proc/self/fd/%d"; | ||
| 103 | char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)]; | ||
| 104 | sprintf (buf, fmt, fd); | ||
| 105 | int chmod_result = chmod (buf, mode); | ||
| 106 | int chmod_errno = errno; | ||
| 107 | close (fd); | ||
| 108 | if (chmod_result == 0) | ||
| 109 | return chmod_result; | ||
| 110 | if (chmod_errno != ENOENT) | ||
| 111 | { | ||
| 112 | errno = chmod_errno; | ||
| 113 | return chmod_result; | ||
| 114 | } | ||
| 115 | # endif | ||
| 116 | /* /proc is not mounted or would not work as in GNU/Linux. */ | ||
| 117 | |||
| 118 | # else | ||
| 119 | int fstatat_result = fstatat (dir, file, &st, AT_SYMLINK_NOFOLLOW); | ||
| 120 | if (fstatat_result != 0) | ||
| 121 | return fstatat_result; | ||
| 122 | if (S_ISLNK (st.st_mode)) | ||
| 123 | { | ||
| 124 | errno = EOPNOTSUPP; | ||
| 125 | return -1; | ||
| 126 | } | ||
| 127 | # endif | ||
| 128 | |||
| 129 | /* Fall back on orig_fchmodat with no flags, despite a possible race. */ | ||
| 130 | flags = 0; | ||
| 131 | } | ||
| 132 | # endif | ||
| 133 | |||
| 134 | return orig_fchmodat (dir, file, mode, flags); | ||
| 135 | } | ||
| 136 | #else | ||
| 137 | # define AT_FUNC_NAME fchmodat | ||
| 138 | # define AT_FUNC_F1 lchmod | ||
| 139 | # define AT_FUNC_F2 chmod | ||
| 140 | # define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW | ||
| 141 | # define AT_FUNC_POST_FILE_PARAM_DECLS , mode_t mode, int flag | ||
| 142 | # define AT_FUNC_POST_FILE_ARGS , mode | ||
| 143 | # include "at-func.c" | ||
| 144 | #endif | ||
diff --git a/lib/gnulib.mk.in b/lib/gnulib.mk.in index 3c01e61b266..d4dc6a3df33 100644 --- a/lib/gnulib.mk.in +++ b/lib/gnulib.mk.in | |||
| @@ -95,6 +95,7 @@ | |||
| 95 | # execinfo \ | 95 | # execinfo \ |
| 96 | # explicit_bzero \ | 96 | # explicit_bzero \ |
| 97 | # faccessat \ | 97 | # faccessat \ |
| 98 | # fchmodat \ | ||
| 98 | # fcntl \ | 99 | # fcntl \ |
| 99 | # fcntl-h \ | 100 | # fcntl-h \ |
| 100 | # fdopendir \ | 101 | # fdopendir \ |
| @@ -1082,6 +1083,7 @@ gl_GNULIB_ENABLED_dirfd = @gl_GNULIB_ENABLED_dirfd@ | |||
| 1082 | gl_GNULIB_ENABLED_euidaccess = @gl_GNULIB_ENABLED_euidaccess@ | 1083 | gl_GNULIB_ENABLED_euidaccess = @gl_GNULIB_ENABLED_euidaccess@ |
| 1083 | gl_GNULIB_ENABLED_getdtablesize = @gl_GNULIB_ENABLED_getdtablesize@ | 1084 | gl_GNULIB_ENABLED_getdtablesize = @gl_GNULIB_ENABLED_getdtablesize@ |
| 1084 | gl_GNULIB_ENABLED_getgroups = @gl_GNULIB_ENABLED_getgroups@ | 1085 | gl_GNULIB_ENABLED_getgroups = @gl_GNULIB_ENABLED_getgroups@ |
| 1086 | gl_GNULIB_ENABLED_lchmod = @gl_GNULIB_ENABLED_lchmod@ | ||
| 1085 | gl_GNULIB_ENABLED_malloca = @gl_GNULIB_ENABLED_malloca@ | 1087 | gl_GNULIB_ENABLED_malloca = @gl_GNULIB_ENABLED_malloca@ |
| 1086 | gl_GNULIB_ENABLED_open = @gl_GNULIB_ENABLED_open@ | 1088 | gl_GNULIB_ENABLED_open = @gl_GNULIB_ENABLED_open@ |
| 1087 | gl_GNULIB_ENABLED_strtoll = @gl_GNULIB_ENABLED_strtoll@ | 1089 | gl_GNULIB_ENABLED_strtoll = @gl_GNULIB_ENABLED_strtoll@ |
| @@ -1586,6 +1588,17 @@ EXTRA_libgnu_a_SOURCES += at-func.c faccessat.c | |||
| 1586 | endif | 1588 | endif |
| 1587 | ## end gnulib module faccessat | 1589 | ## end gnulib module faccessat |
| 1588 | 1590 | ||
| 1591 | ## begin gnulib module fchmodat | ||
| 1592 | ifeq (,$(OMIT_GNULIB_MODULE_fchmodat)) | ||
| 1593 | |||
| 1594 | |||
| 1595 | EXTRA_DIST += at-func.c fchmodat.c | ||
| 1596 | |||
| 1597 | EXTRA_libgnu_a_SOURCES += at-func.c fchmodat.c | ||
| 1598 | |||
| 1599 | endif | ||
| 1600 | ## end gnulib module fchmodat | ||
| 1601 | |||
| 1589 | ## begin gnulib module fcntl | 1602 | ## begin gnulib module fcntl |
| 1590 | ifeq (,$(OMIT_GNULIB_MODULE_fcntl)) | 1603 | ifeq (,$(OMIT_GNULIB_MODULE_fcntl)) |
| 1591 | 1604 | ||
| @@ -1936,6 +1949,19 @@ EXTRA_DIST += inttypes.in.h | |||
| 1936 | endif | 1949 | endif |
| 1937 | ## end gnulib module inttypes-incomplete | 1950 | ## end gnulib module inttypes-incomplete |
| 1938 | 1951 | ||
| 1952 | ## begin gnulib module lchmod | ||
| 1953 | ifeq (,$(OMIT_GNULIB_MODULE_lchmod)) | ||
| 1954 | |||
| 1955 | ifneq (,$(gl_GNULIB_ENABLED_lchmod)) | ||
| 1956 | |||
| 1957 | endif | ||
| 1958 | EXTRA_DIST += lchmod.c | ||
| 1959 | |||
| 1960 | EXTRA_libgnu_a_SOURCES += lchmod.c | ||
| 1961 | |||
| 1962 | endif | ||
| 1963 | ## end gnulib module lchmod | ||
| 1964 | |||
| 1939 | ## begin gnulib module libc-config | 1965 | ## begin gnulib module libc-config |
| 1940 | ifeq (,$(OMIT_GNULIB_MODULE_libc-config)) | 1966 | ifeq (,$(OMIT_GNULIB_MODULE_libc-config)) |
| 1941 | 1967 | ||
diff --git a/lib/lchmod.c b/lib/lchmod.c new file mode 100644 index 00000000000..e1132116234 --- /dev/null +++ b/lib/lchmod.c | |||
| @@ -0,0 +1,110 @@ | |||
| 1 | /* Implement lchmod on platforms where it does not work correctly. | ||
| 2 | |||
| 3 | Copyright 2020 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | This program is free software: you can redistribute it and/or modify | ||
| 6 | it under the terms of the GNU General Public License as published by | ||
| 7 | the Free Software Foundation; either version 3 of the License, or | ||
| 8 | (at your option) any later version. | ||
| 9 | |||
| 10 | This program is distributed in the hope that it will be useful, | ||
| 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 13 | GNU General Public License for more details. | ||
| 14 | |||
| 15 | You should have received a copy of the GNU General Public License | ||
| 16 | along with this program. If not, see <https://www.gnu.org/licenses/>. */ | ||
| 17 | |||
| 18 | /* written by Paul Eggert */ | ||
| 19 | |||
| 20 | #include <config.h> | ||
| 21 | |||
| 22 | /* Specification. */ | ||
| 23 | #include <sys/stat.h> | ||
| 24 | |||
| 25 | #include <errno.h> | ||
| 26 | #include <fcntl.h> | ||
| 27 | #include <stdio.h> | ||
| 28 | #include <unistd.h> | ||
| 29 | |||
| 30 | #ifdef __osf__ | ||
| 31 | /* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc | ||
| 32 | eliminates this include because of the preliminary #include <sys/stat.h> | ||
| 33 | above. */ | ||
| 34 | # include "sys/stat.h" | ||
| 35 | #else | ||
| 36 | # include <sys/stat.h> | ||
| 37 | #endif | ||
| 38 | |||
| 39 | #include <intprops.h> | ||
| 40 | |||
| 41 | /* Work like chmod, except when FILE is a symbolic link. | ||
| 42 | In that case, on systems where permissions on symbolic links are unsupported | ||
| 43 | (such as Linux), set errno to EOPNOTSUPP and return -1. */ | ||
| 44 | |||
| 45 | int | ||
| 46 | lchmod (char const *file, mode_t mode) | ||
| 47 | { | ||
| 48 | #if defined O_PATH && defined AT_EMPTY_PATH | ||
| 49 | /* Open a file descriptor with O_NOFOLLOW, to make sure we don't | ||
| 50 | follow symbolic links, if /proc is mounted. O_PATH is used to | ||
| 51 | avoid a failure if the file is not readable. | ||
| 52 | Cf. <https://sourceware.org/bugzilla/show_bug.cgi?id=14578> */ | ||
| 53 | int fd = open (file, O_PATH | O_NOFOLLOW | O_CLOEXEC); | ||
| 54 | if (fd < 0) | ||
| 55 | return fd; | ||
| 56 | |||
| 57 | /* Up to Linux 5.3 at least, when FILE refers to a symbolic link, the | ||
| 58 | chmod call below will change the permissions of the symbolic link | ||
| 59 | - which is undesired - and on many file systems (ext4, btrfs, jfs, | ||
| 60 | xfs, ..., but not reiserfs) fail with error EOPNOTSUPP - which is | ||
| 61 | misleading. Therefore test for a symbolic link explicitly. | ||
| 62 | Use fstatat because fstat does not work on O_PATH descriptors | ||
| 63 | before Linux 3.6. */ | ||
| 64 | struct stat st; | ||
| 65 | if (fstatat (fd, "", &st, AT_EMPTY_PATH) != 0) | ||
| 66 | { | ||
| 67 | int stat_errno = errno; | ||
| 68 | close (fd); | ||
| 69 | errno = stat_errno; | ||
| 70 | return -1; | ||
| 71 | } | ||
| 72 | if (S_ISLNK (st.st_mode)) | ||
| 73 | { | ||
| 74 | close (fd); | ||
| 75 | errno = EOPNOTSUPP; | ||
| 76 | return -1; | ||
| 77 | } | ||
| 78 | |||
| 79 | # if defined __linux__ || defined __ANDROID__ | ||
| 80 | static char const fmt[] = "/proc/self/fd/%d"; | ||
| 81 | char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)]; | ||
| 82 | sprintf (buf, fmt, fd); | ||
| 83 | int chmod_result = chmod (buf, mode); | ||
| 84 | int chmod_errno = errno; | ||
| 85 | close (fd); | ||
| 86 | if (chmod_result == 0) | ||
| 87 | return chmod_result; | ||
| 88 | if (chmod_errno != ENOENT) | ||
| 89 | { | ||
| 90 | errno = chmod_errno; | ||
| 91 | return chmod_result; | ||
| 92 | } | ||
| 93 | # endif | ||
| 94 | /* /proc is not mounted or would not work as in GNU/Linux. */ | ||
| 95 | |||
| 96 | #elif HAVE_LSTAT | ||
| 97 | struct stat st; | ||
| 98 | int lstat_result = lstat (file, &st); | ||
| 99 | if (lstat_result != 0) | ||
| 100 | return lstat_result; | ||
| 101 | if (S_ISLNK (st.st_mode)) | ||
| 102 | { | ||
| 103 | errno = EOPNOTSUPP; | ||
| 104 | return -1; | ||
| 105 | } | ||
| 106 | #endif | ||
| 107 | |||
| 108 | /* Fall back on chmod, despite a possible race. */ | ||
| 109 | return chmod (file, mode); | ||
| 110 | } | ||