diff options
author | Paul Eggert <eggert@cs.ucla.edu> | 2022-07-03 18:09:56 -0500 |
---|---|---|
committer | Paul Eggert <eggert@cs.ucla.edu> | 2022-07-03 18:17:41 -0500 |
commit | 40dba285721e4a83482615e40c1d7521275ccab7 (patch) | |
tree | a3b395a9f798da75de6af6e83601a27e94c1f597 | |
parent | bfdc4d6ee4811c34d8756fcca7895f5d2eed6946 (diff) | |
download | sed-40dba285721e4a83482615e40c1d7521275ccab7.tar.gz |
sed: fix infloop with symlink cycles
* bootstrap.conf (gnulib_modules):
Add eloop-threshold, idx, minmax, readlink.
* configure.ac: Do not check for lstat; no longer needed.
(ENABLE_FOLLOW_SYMLINKS): Remove; all uses removed.
(TEST_SYMLINKS): Depend only on readlink.
* sed/utils.c: Include eloop-threshold.h, idx.h, minmax.h.
(SSIZE_IDX_MAX): New macro.
(follow_symlink): Rewrite to not loop when given a symlink cycle.
Do not use lstat, since readlink suffices. Use just one memory
buffer, not two; this simplifies memory management.
* testsuite/follow-symlinks.sh: Adjust diagnostics to
to match revised behavior. Test for symlink loops.
-rw-r--r-- | NEWS | 4 | ||||
-rw-r--r-- | bootstrap.conf | 4 | ||||
-rw-r--r-- | configure.ac | 9 | ||||
-rw-r--r-- | sed/sed.c | 4 | ||||
-rw-r--r-- | sed/utils.c | 124 | ||||
-rw-r--r-- | testsuite/follow-symlinks.sh | 8 |
6 files changed, 87 insertions, 66 deletions
@@ -4,6 +4,10 @@ GNU sed NEWS -*- outline -*- ** Bug fixes + 'sed --follow-symlinks -i' no longer loops forever when its operand + is a symbolic link cycle. + [bug introduced in sed 4.2] + a program with an execution line longer than 2GB can no longer trigger an out-of-bounds memory write. diff --git a/bootstrap.conf b/bootstrap.conf index 9285bd6..ea5c2f9 100644 --- a/bootstrap.conf +++ b/bootstrap.conf @@ -29,12 +29,14 @@ btowc c-ctype closeout dfa +eloop-threshold extensions fwriting getdelim gettext-h git-version-gen gitlog-to-changelog +idx ignore-value localcharset manywarnings @@ -43,10 +45,12 @@ mbrtowc mbsinit memchr memrchr +minmax mkostemp obstack perl progname +readlink readme-release regex rename diff --git a/configure.ac b/configure.ac index cfea826..62aad18 100644 --- a/configure.ac +++ b/configure.ac @@ -117,16 +117,11 @@ AC_TYPE_SIZE_T AM_GNU_GETTEXT_VERSION([0.19.2]) AM_GNU_GETTEXT([external]) -AC_CHECK_FUNCS_ONCE(isatty isascii memcpy strchr strtoul lstat readlink +AC_CHECK_FUNCS_ONCE(isatty isascii memcpy strchr strtoul readlink popen pathconf fchown fchmod setlocale) -# Check whether we are able to follow symlinks -if test "$ac_cv_func_lstat:$ac_cv_func_readlink" = yes:yes; then - AC_DEFINE([ENABLE_FOLLOW_SYMLINKS], , - [Follow symlinks when processing in place]) -fi AM_CONDITIONAL([TEST_SYMLINKS], - [test "$ac_cv_func_lstat:$ac_cv_func_readlink" = yes:yes]) + [test "$ac_cv_func_readlink" = yes]) AC_ARG_ENABLE(i18n, [ --disable-i18n disable internationalization (default=enabled)], , @@ -143,7 +143,7 @@ Usage: %s [OPTION]... {script-only-if-no-other-script} [input-file]...\n\ fprintf (out, _(" -f script-file, --file=script-file\n\ add the contents of script-file to the commands" \ " to be executed\n")); -#ifdef ENABLE_FOLLOW_SYMLINKS +#ifdef HAVE_READLINK fprintf (out, _(" --follow-symlinks\n\ follow symlinks when processing in place\n")); #endif @@ -212,7 +212,7 @@ main (int argc, char **argv) {"unbuffered", 0, NULL, 'u'}, {"version", 0, NULL, 'v'}, {"help", 0, NULL, 'h'}, -#ifdef ENABLE_FOLLOW_SYMLINKS +#ifdef HAVE_READLINK {"follow-symlinks", 0, NULL, 'F'}, #endif {NULL, 0, NULL, 0} diff --git a/sed/utils.c b/sed/utils.c index 823de8d..4bd6587 100644 --- a/sed/utils.c +++ b/sed/utils.c @@ -27,12 +27,21 @@ #include <limits.h> #include "binary-io.h" +#include "eloop-threshold.h" +#include "idx.h" +#include "minmax.h" #include "unlocked-io.h" #include "utils.h" #include "progname.h" #include "fwriting.h" #include "xalloc.h" +#ifdef SSIZE_MAX +# define SSIZE_IDX_MAX MIN (SSIZE_MAX, IDX_MAX) +#else +# define SSIZE_IDX_MAX IDX_MAX +#endif + #if O_BINARY extern bool binary_mode; #endif @@ -304,83 +313,88 @@ do_ck_fclose (FILE *fp, char const *name) panic ("couldn't close %s: %s", name, strerror (errno)); } -/* Follow symlink and panic if something fails. Return the ultimate - symlink target, stored in a temporary buffer that the caller should - not free. */ +/* Follow symlink FNAME and return the ultimate target, stored in a + temporary buffer that the caller should not free. Return FNAME if + it is not a symlink. Panic if a symlink loop is found. */ const char * follow_symlink (const char *fname) { -#ifdef ENABLE_FOLLOW_SYMLINKS - static char *buf1, *buf2; - static int buf_size; + /* The file name, as adjusted so far by replacing symlinks with + their contents. Only the last file name component is replaced, + as we need not do all the work of realpath. */ - struct stat statbuf; - const char *buf = fname, *c; - int rc; + /* FIXME: We should get a file descriptor on the parent directory, + to avoid resolving that directory name more than once (which can + lead to races). Perhaps someday the Gnulib 'supersede' module + can get a function openat_supersede that will do this for us. */ + char const *fn = fname; - if (buf_size == 0) - { - buf1 = xzalloc (PATH_MAX + 1); - buf2 = xzalloc (PATH_MAX + 1); - buf_size = PATH_MAX + 1; - } +#ifdef HAVE_READLINK + static char *buf; + static idx_t buf_size; + + idx_t buf_used = 0; - while ((rc = lstat (buf, &statbuf)) == 0 - && (statbuf.st_mode & S_IFLNK) == S_IFLNK) + for (idx_t num_links = 0; ; num_links++) { - if (buf == buf2) + ssize_t linklen; + idx_t newlen; + char const *c; + + /* Put symlink contents into BUF + BUF_USED. */ + while ((linklen = (buf_used < buf_size + ? readlink (fn, buf + buf_used, buf_size - buf_used) + : 0)) + == buf_size) { - strcpy (buf1, buf2); - buf = buf1; + buf = xpalloc (buf, &buf_size, 1, SSIZE_IDX_MAX, 1); + if (num_links) + fn = buf; } - - while ((rc = readlink (buf, buf2, buf_size)) == buf_size) + if (linklen < 0) { - buf_size *= 2; - buf1 = xrealloc (buf1, buf_size); - buf2 = xrealloc (buf2, buf_size); + if (errno == EINVAL) + break; + panic (_("couldn't readlink %s: %s"), fn, strerror (errno)); } - if (rc < 0) - panic (_("couldn't follow symlink %s: %s"), buf, strerror (errno)); - else - buf2 [rc] = '\0'; + if (__eloop_threshold () <= num_links) + panic (_("couldn't follow symlink %s: %s"), fname, strerror (ELOOP)); - if (buf2[0] != '/' && (c = strrchr (buf, '/')) != NULL) + if ((linklen == 0 || buf[buf_used] != '/') && (c = strrchr (fn, '/'))) { - /* Need to handle relative paths with care. Reallocate buf1 and - buf2 to be big enough. */ - int len = c - buf + 1; - if (len + rc + 1 > buf_size) + /* A relative symlink not from the working directory. + Make sure BUF is big enough. */ + idx_t dirlen = c - fn + 1; + newlen = dirlen + linklen; + if (buf_size <= newlen) { - buf_size = len + rc + 1; - buf1 = xrealloc (buf1, buf_size); - buf2 = xrealloc (buf2, buf_size); + buf = xpalloc (buf, &buf_size, newlen + 1 - buf_size, + SSIZE_IDX_MAX, 1); + if (num_links) + fn = buf; } - /* Always store the new path in buf1. */ - if (buf != buf1) - memcpy (buf1, buf, len); - - /* Tack the relative symlink at the end of buf1. */ - memcpy (buf1 + len, buf2, rc + 1); - buf = buf1; + /* Store the new file name in BUF. Beware overlap. */ + memmove (buf + dirlen, buf + buf_used, linklen); + if (fn != buf) + memcpy (buf, fn, dirlen); } else { - /* Use buf2 as the buffer, it saves a strcpy if it is not pointing to - another link. It works for absolute symlinks, and as long as - symlinks do not leave the current directory. */ - buf = buf2; + /* A symlink to an absolute file name, or a relative symlink + from the working directory. The new file name is simply + the symlink contents. */ + memmove (buf, buf + buf_used, linklen); + newlen = linklen; } - } - if (rc < 0) - panic (_("cannot stat %s: %s"), buf, strerror (errno)); + buf[newlen] = '\0'; + buf_used = newlen + 1; + fn = buf; + } +#endif - return buf; -#else - return fname; -#endif /* ENABLE_FOLLOW_SYMLINKS */ + return fn; } /* Panic on failing rename */ diff --git a/testsuite/follow-symlinks.sh b/testsuite/follow-symlinks.sh index 9066876..880a80e 100644 --- a/testsuite/follow-symlinks.sh +++ b/testsuite/follow-symlinks.sh @@ -52,9 +52,9 @@ sed --follow-symlinks -n 'F' la1 la2 > out-two-symlinks || fail=1 compare_ exp-two-symlinks out-two-symlinks || fail=1 # non-existing input with --follow-symlink -# implementation note: lstat() will be called before open(), thus 'cannot stat'. +# implementation note: readlink called before open, thus "couldn't readlink" cat <<\EOF >exp-stat || framework_failure_ -sed: cannot stat badfile: +sed: couldn't readlink badfile: EOF returns_ 4 sed --follow-symlinks 'F' badfile >/dev/null 2>err-stat || fail=1 @@ -69,4 +69,8 @@ echo "$PWD/a" > exp-la-abs || framework_failure_ sed -n --follow-symlinks 'F' la-abs > out-la-abs || fail=1 compare_ exp-la-abs out-la-abs || fail=1 +# symlink loop +ln -s la-loop la-loop || framework_failure_ +sed --follow-symlinks -i s/a/b/ la-loop && fail=1 + Exit $fail |