diff options
| author | Paul Eggert | 2012-11-13 20:55:41 -0800 |
|---|---|---|
| committer | Paul Eggert | 2012-11-13 20:55:41 -0800 |
| commit | 73dcdb9f30cb94a3183db54d9b463370c3978d4d (patch) | |
| tree | 216d47d5bc96bce2a4ca87a57967b0e1c3c151f2 /src/lread.c | |
| parent | 9c3912d3d9aaa1e20e3f7168f5764695ad5e43fd (diff) | |
| download | emacs-73dcdb9f30cb94a3183db54d9b463370c3978d4d.tar.gz emacs-73dcdb9f30cb94a3183db54d9b463370c3978d4d.zip | |
Use faccessat, not access, when checking file permissions.
This fixes a bug that has been present in Emacs since its creation.
It was reported by Chris Torek in 1983 even before GNU Emacs existed,
which must set some sort of record. (Torek's bug report was against
a predecessor of GNU Emacs, but GNU Emacs happened to have the
same common flaw.) See Torek's Usenet posting
"setuid/setgid programs & Emacs" Article-I.D.: sri-arpa.858
Posted: Fri Apr 8 14:18:56 1983.
* .bzrignore: Add lib/fcntl.h.
* configure.ac (euidaccess): Remove check; gnulib does this for us now.
(gl_FCNTL_O_FLAGS): Define a dummy version.
* lib/at-func.c, lib/euidaccess.c, lib/faccessat.c, lib/fcntl.in.h:
* lib/getgroups.c, lib/group-member.c, lib/root-uid.h:
* lib/xalloc-oversized.h, m4/euidaccess.m4, m4/faccessat.m4:
* m4/fcntl_h.m4, m4/getgroups.m4, m4/group-member.m4:
New files, from gnulib.
* lib/gnulib.mk, m4/gnulib-comp.m4: Regenerate.
* admin/merge-gnulib (GNULIB_MODULES): Add faccessat.
(GNULIB_TOOL_FLAGS): Avoid at-internal, fchdir, malloc-posix,
openat-die, openat-h, save-cwd. Do not avoid fcntl-h.
Omit gnulib's m4/fcntl-o.m4.
* nt/inc/ms-w32.h (AT_FDCWD, AT_EACCESS): New symbols.
(access): Remove.
(faccessat): New macro.
* src/Makefile.in (LIB_EACCESS): New macro.
(LIBES): Use it.
* src/callproc.c (init_callproc):
* src/charset.c (init_charset):
* src/fileio.c (check_existing, check_executable, check_writable)
(Ffile_readable_p):
* src/lread.c (openp, load_path_check):
* src/process.c (allocate_pty):
* src/xrdb.c (file_p):
Use effective UID when checking permissions, not real UID.
* src/callproc.c (init_callproc):
* src/charset.c (init_charset):
* src/lread.c (load_path_check, init_lread):
Test whether directories are accessible, not merely whether they exist.
* src/conf_post.h (GNULIB_SUPPORT_ONLY_AT_FDCWD): New macro.
* src/fileio.c (check_existing, check_executable, check_writable)
(Ffile_readable_p):
Use symbolic names instead of integers for the flags, as they're
portable now.
(check_writable): New arg AMODE. All uses changed.
Set errno on failure.
(Ffile_readable_p): Use faccessat, not stat + open + close.
(Ffile_writable_p): No need to call check_existing + check_writable.
Just call check_writable and then look at errno. This saves a syscall.
dir should never be nil; replace an unnecessary runtime check
with an eassert. When checking the parent directory of a nonexistent
file, check that the directory is searchable as well as writable, as
we can't create files in unsearchable directories.
(file_directory_p): New function, which uses 'stat' on most platforms
but faccessat with D_OK (for efficiency) if WINDOWSNT.
(Ffile_directory_p, Fset_file_times): Use it.
(file_accessible_directory_p): New function, which uses a single
syscall for efficiency.
(Ffile_accessible_directory_p): Use it.
* src/xrdb.c (file_p): Use file_directory_p.
* src/lisp.h (file_directory_p, file_accessible_directory_p): New decls.
* src/lread.c (openp): When opening a file, use fstat rather than
stat, as that avoids a permissions race. When not opening a file,
use file_directory_p rather than stat.
(dir_warning): First arg is now a usage string, not a format.
Use errno. All uses changed.
* src/nsterm.m (ns_term_init): Remove unnecessary call to file-readable
that merely introduced a race.
* src/process.c, src/sysdep.c, src/term.c: All uses of '#ifdef O_NONBLOCK'
changed to '#if O_NONBLOCK', to accommodate gnulib O_* style,
and similarly for the other O_* flags.
* src/w32.c (sys_faccessat): Rename from sys_access and switch to
faccessat's API. All uses changed.
* src/xrdb.c: Do not include <sys/stat.h>; no longer needed.
(magic_db): Rename from magic_file_p.
(magic_db, search_magic_path): Return an XrmDatabase rather than a
char *, so that we don't have to test for file existence
separately from opening the file for reading. This removes a race
fixes a permission-checking problem, and simplifies the code.
All uses changed.
(file_p): Remove; no longer needed.
Fixes: debbugs:12632
Diffstat (limited to 'src/lread.c')
| -rw-r--r-- | src/lread.c | 92 |
1 files changed, 50 insertions, 42 deletions
diff --git a/src/lread.c b/src/lread.c index 3a82e0057e2..5859a2f85a9 100644 --- a/src/lread.c +++ b/src/lread.c | |||
| @@ -1403,7 +1403,7 @@ Returns the file's name in absolute form, or nil if not found. | |||
| 1403 | If SUFFIXES is non-nil, it should be a list of suffixes to append to | 1403 | If SUFFIXES is non-nil, it should be a list of suffixes to append to |
| 1404 | file name when searching. | 1404 | file name when searching. |
| 1405 | If non-nil, PREDICATE is used instead of `file-readable-p'. | 1405 | If non-nil, PREDICATE is used instead of `file-readable-p'. |
| 1406 | PREDICATE can also be an integer to pass to the access(2) function, | 1406 | PREDICATE can also be an integer to pass to the faccessat(2) function, |
| 1407 | in which case file-name-handlers are ignored. | 1407 | in which case file-name-handlers are ignored. |
| 1408 | This function will normally skip directories, so if you want it to find | 1408 | This function will normally skip directories, so if you want it to find |
| 1409 | directories, make sure the PREDICATE function returns `dir-ok' for them. */) | 1409 | directories, make sure the PREDICATE function returns `dir-ok' for them. */) |
| @@ -1441,7 +1441,6 @@ static Lisp_Object Qdir_ok; | |||
| 1441 | int | 1441 | int |
| 1442 | openp (Lisp_Object path, Lisp_Object str, Lisp_Object suffixes, Lisp_Object *storeptr, Lisp_Object predicate) | 1442 | openp (Lisp_Object path, Lisp_Object str, Lisp_Object suffixes, Lisp_Object *storeptr, Lisp_Object predicate) |
| 1443 | { | 1443 | { |
| 1444 | int fd; | ||
| 1445 | ptrdiff_t fn_size = 100; | 1444 | ptrdiff_t fn_size = 100; |
| 1446 | char buf[100]; | 1445 | char buf[100]; |
| 1447 | char *fn = buf; | 1446 | char *fn = buf; |
| @@ -1496,7 +1495,6 @@ openp (Lisp_Object path, Lisp_Object str, Lisp_Object suffixes, Lisp_Object *sto | |||
| 1496 | { | 1495 | { |
| 1497 | ptrdiff_t fnlen, lsuffix = SBYTES (XCAR (tail)); | 1496 | ptrdiff_t fnlen, lsuffix = SBYTES (XCAR (tail)); |
| 1498 | Lisp_Object handler; | 1497 | Lisp_Object handler; |
| 1499 | bool exists; | ||
| 1500 | 1498 | ||
| 1501 | /* Concatenate path element/specified name with the suffix. | 1499 | /* Concatenate path element/specified name with the suffix. |
| 1502 | If the directory starts with /:, remove that. */ | 1500 | If the directory starts with /:, remove that. */ |
| @@ -1520,6 +1518,7 @@ openp (Lisp_Object path, Lisp_Object str, Lisp_Object suffixes, Lisp_Object *sto | |||
| 1520 | handler = Ffind_file_name_handler (string, Qfile_exists_p); | 1518 | handler = Ffind_file_name_handler (string, Qfile_exists_p); |
| 1521 | if ((!NILP (handler) || !NILP (predicate)) && !NATNUMP (predicate)) | 1519 | if ((!NILP (handler) || !NILP (predicate)) && !NATNUMP (predicate)) |
| 1522 | { | 1520 | { |
| 1521 | bool exists; | ||
| 1523 | if (NILP (predicate)) | 1522 | if (NILP (predicate)) |
| 1524 | exists = !NILP (Ffile_readable_p (string)); | 1523 | exists = !NILP (Ffile_readable_p (string)); |
| 1525 | else | 1524 | else |
| @@ -1541,37 +1540,40 @@ openp (Lisp_Object path, Lisp_Object str, Lisp_Object suffixes, Lisp_Object *sto | |||
| 1541 | } | 1540 | } |
| 1542 | else | 1541 | else |
| 1543 | { | 1542 | { |
| 1544 | #ifndef WINDOWSNT | 1543 | int fd; |
| 1545 | struct stat st; | ||
| 1546 | #endif | ||
| 1547 | const char *pfn; | 1544 | const char *pfn; |
| 1548 | 1545 | ||
| 1549 | encoded_fn = ENCODE_FILE (string); | 1546 | encoded_fn = ENCODE_FILE (string); |
| 1550 | pfn = SSDATA (encoded_fn); | 1547 | pfn = SSDATA (encoded_fn); |
| 1551 | #ifdef WINDOWSNT | ||
| 1552 | exists = access (pfn, F_OK) == 0 && access (pfn, D_OK) < 0; | ||
| 1553 | #else | ||
| 1554 | exists = (stat (pfn, &st) == 0 && ! S_ISDIR (st.st_mode)); | ||
| 1555 | #endif | ||
| 1556 | if (exists) | ||
| 1557 | { | ||
| 1558 | /* Check that we can access or open it. */ | ||
| 1559 | if (NATNUMP (predicate)) | ||
| 1560 | fd = (((XFASTINT (predicate) & ~INT_MAX) == 0 | ||
| 1561 | && access (pfn, XFASTINT (predicate)) == 0) | ||
| 1562 | ? 1 : -1); | ||
| 1563 | else | ||
| 1564 | fd = emacs_open (pfn, O_RDONLY, 0); | ||
| 1565 | 1548 | ||
| 1566 | if (fd >= 0) | 1549 | /* Check that we can access or open it. */ |
| 1550 | if (NATNUMP (predicate)) | ||
| 1551 | fd = (((XFASTINT (predicate) & ~INT_MAX) == 0 | ||
| 1552 | && (faccessat (AT_FDCWD, pfn, XFASTINT (predicate), | ||
| 1553 | AT_EACCESS) | ||
| 1554 | == 0) | ||
| 1555 | && ! file_directory_p (pfn)) | ||
| 1556 | ? 1 : -1); | ||
| 1557 | else | ||
| 1558 | { | ||
| 1559 | struct stat st; | ||
| 1560 | fd = emacs_open (pfn, O_RDONLY, 0); | ||
| 1561 | if (0 <= fd | ||
| 1562 | && (fstat (fd, &st) != 0 || S_ISDIR (st.st_mode))) | ||
| 1567 | { | 1563 | { |
| 1568 | /* We succeeded; return this descriptor and filename. */ | 1564 | emacs_close (fd); |
| 1569 | if (storeptr) | 1565 | fd = -1; |
| 1570 | *storeptr = string; | ||
| 1571 | UNGCPRO; | ||
| 1572 | return fd; | ||
| 1573 | } | 1566 | } |
| 1574 | } | 1567 | } |
| 1568 | |||
| 1569 | if (fd >= 0) | ||
| 1570 | { | ||
| 1571 | /* We succeeded; return this descriptor and filename. */ | ||
| 1572 | if (storeptr) | ||
| 1573 | *storeptr = string; | ||
| 1574 | UNGCPRO; | ||
| 1575 | return fd; | ||
| 1576 | } | ||
| 1575 | } | 1577 | } |
| 1576 | } | 1578 | } |
| 1577 | if (absolute) | 1579 | if (absolute) |
| @@ -4087,9 +4089,8 @@ load_path_check (void) | |||
| 4087 | if (STRINGP (dirfile)) | 4089 | if (STRINGP (dirfile)) |
| 4088 | { | 4090 | { |
| 4089 | dirfile = Fdirectory_file_name (dirfile); | 4091 | dirfile = Fdirectory_file_name (dirfile); |
| 4090 | if (access (SSDATA (dirfile), 0) < 0) | 4092 | if (! file_accessible_directory_p (SSDATA (dirfile))) |
| 4091 | dir_warning ("Warning: Lisp directory `%s' does not exist.\n", | 4093 | dir_warning ("Lisp directory", XCAR (path_tail)); |
| 4092 | XCAR (path_tail)); | ||
| 4093 | } | 4094 | } |
| 4094 | } | 4095 | } |
| 4095 | } | 4096 | } |
| @@ -4201,11 +4202,11 @@ init_lread (void) | |||
| 4201 | Lisp_Object tem, tem1; | 4202 | Lisp_Object tem, tem1; |
| 4202 | 4203 | ||
| 4203 | /* Add to the path the lisp subdir of the installation | 4204 | /* Add to the path the lisp subdir of the installation |
| 4204 | dir, if it exists. Note: in out-of-tree builds, | 4205 | dir, if it is accessible. Note: in out-of-tree builds, |
| 4205 | this directory is empty save for Makefile. */ | 4206 | this directory is empty save for Makefile. */ |
| 4206 | tem = Fexpand_file_name (build_string ("lisp"), | 4207 | tem = Fexpand_file_name (build_string ("lisp"), |
| 4207 | Vinstallation_directory); | 4208 | Vinstallation_directory); |
| 4208 | tem1 = Ffile_exists_p (tem); | 4209 | tem1 = Ffile_accessible_directory_p (tem); |
| 4209 | if (!NILP (tem1)) | 4210 | if (!NILP (tem1)) |
| 4210 | { | 4211 | { |
| 4211 | if (NILP (Fmember (tem, Vload_path))) | 4212 | if (NILP (Fmember (tem, Vload_path))) |
| @@ -4222,10 +4223,10 @@ init_lread (void) | |||
| 4222 | Lisp dirs instead. */ | 4223 | Lisp dirs instead. */ |
| 4223 | Vload_path = nconc2 (Vload_path, dump_path); | 4224 | Vload_path = nconc2 (Vload_path, dump_path); |
| 4224 | 4225 | ||
| 4225 | /* Add leim under the installation dir, if it exists. */ | 4226 | /* Add leim under the installation dir, if it is accessible. */ |
| 4226 | tem = Fexpand_file_name (build_string ("leim"), | 4227 | tem = Fexpand_file_name (build_string ("leim"), |
| 4227 | Vinstallation_directory); | 4228 | Vinstallation_directory); |
| 4228 | tem1 = Ffile_exists_p (tem); | 4229 | tem1 = Ffile_accessible_directory_p (tem); |
| 4229 | if (!NILP (tem1)) | 4230 | if (!NILP (tem1)) |
| 4230 | { | 4231 | { |
| 4231 | if (NILP (Fmember (tem, Vload_path))) | 4232 | if (NILP (Fmember (tem, Vload_path))) |
| @@ -4237,7 +4238,7 @@ init_lread (void) | |||
| 4237 | { | 4238 | { |
| 4238 | tem = Fexpand_file_name (build_string ("site-lisp"), | 4239 | tem = Fexpand_file_name (build_string ("site-lisp"), |
| 4239 | Vinstallation_directory); | 4240 | Vinstallation_directory); |
| 4240 | tem1 = Ffile_exists_p (tem); | 4241 | tem1 = Ffile_accessible_directory_p (tem); |
| 4241 | if (!NILP (tem1)) | 4242 | if (!NILP (tem1)) |
| 4242 | { | 4243 | { |
| 4243 | if (NILP (Fmember (tem, Vload_path))) | 4244 | if (NILP (Fmember (tem, Vload_path))) |
| @@ -4282,7 +4283,7 @@ init_lread (void) | |||
| 4282 | { | 4283 | { |
| 4283 | tem = Fexpand_file_name (build_string ("site-lisp"), | 4284 | tem = Fexpand_file_name (build_string ("site-lisp"), |
| 4284 | Vsource_directory); | 4285 | Vsource_directory); |
| 4285 | tem1 = Ffile_exists_p (tem); | 4286 | tem1 = Ffile_accessible_directory_p (tem); |
| 4286 | if (!NILP (tem1)) | 4287 | if (!NILP (tem1)) |
| 4287 | { | 4288 | { |
| 4288 | if (NILP (Fmember (tem, Vload_path))) | 4289 | if (NILP (Fmember (tem, Vload_path))) |
| @@ -4338,21 +4339,28 @@ init_lread (void) | |||
| 4338 | Vloads_in_progress = Qnil; | 4339 | Vloads_in_progress = Qnil; |
| 4339 | } | 4340 | } |
| 4340 | 4341 | ||
| 4341 | /* Print a warning, using format string FORMAT, that directory DIRNAME | 4342 | /* Print a warning that directory intended for use USE and with name |
| 4342 | does not exist. Print it on stderr and put it in *Messages*. */ | 4343 | DIRNAME cannot be accessed. On entry, errno should correspond to |
| 4344 | the access failure. Print the warning on stderr and put it in | ||
| 4345 | *Messages*. */ | ||
| 4343 | 4346 | ||
| 4344 | void | 4347 | void |
| 4345 | dir_warning (const char *format, Lisp_Object dirname) | 4348 | dir_warning (char const *use, Lisp_Object dirname) |
| 4346 | { | 4349 | { |
| 4347 | fprintf (stderr, format, SDATA (dirname)); | 4350 | static char const format[] = "Warning: %s `%s': %s\n"; |
| 4351 | int access_errno = errno; | ||
| 4352 | fprintf (stderr, format, use, SSDATA (dirname), strerror (access_errno)); | ||
| 4348 | 4353 | ||
| 4349 | /* Don't log the warning before we've initialized!! */ | 4354 | /* Don't log the warning before we've initialized!! */ |
| 4350 | if (initialized) | 4355 | if (initialized) |
| 4351 | { | 4356 | { |
| 4357 | char const *diagnostic = emacs_strerror (access_errno); | ||
| 4352 | USE_SAFE_ALLOCA; | 4358 | USE_SAFE_ALLOCA; |
| 4353 | char *buffer = SAFE_ALLOCA (SBYTES (dirname) | 4359 | char *buffer = SAFE_ALLOCA (sizeof format - 3 * (sizeof "%s" - 1) |
| 4354 | + strlen (format) - (sizeof "%s" - 1) + 1); | 4360 | + strlen (use) + SBYTES (dirname) |
| 4355 | ptrdiff_t message_len = esprintf (buffer, format, SDATA (dirname)); | 4361 | + strlen (diagnostic)); |
| 4362 | ptrdiff_t message_len = esprintf (buffer, format, use, SSDATA (dirname), | ||
| 4363 | diagnostic); | ||
| 4356 | message_dolog (buffer, message_len, 0, STRING_MULTIBYTE (dirname)); | 4364 | message_dolog (buffer, message_len, 0, STRING_MULTIBYTE (dirname)); |
| 4357 | SAFE_FREE (); | 4365 | SAFE_FREE (); |
| 4358 | } | 4366 | } |