diff options
author | Florian Larysch <fl@n621.de> | 2016-10-08 17:55:55 +0200 |
---|---|---|
committer | Justin M. Keyes <justinkz@gmail.com> | 2016-10-26 13:05:25 +0200 |
commit | 2a6c5bb0c4b03a9da81dae64d37c9912e448eaf0 (patch) | |
tree | 0fd9119bf18da9f765ae85b01747a853f2f8906b | |
parent | 0f32088ea23fbbe9557c89a9e075f2e9b9e158a4 (diff) | |
download | rneovim-2a6c5bb0c4b03a9da81dae64d37c9912e448eaf0.tar.gz rneovim-2a6c5bb0c4b03a9da81dae64d37c9912e448eaf0.tar.bz2 rneovim-2a6c5bb0c4b03a9da81dae64d37c9912e448eaf0.zip |
modeline: Handle version number overflow. #5450
Closes #5449
A file containing the string "vim" followed by a very large number in a modeline
location will trigger an overflow in getdigits() which is called by
chk_modeline() when trying to parse the version number.
Add getdigits_safe(), which does not assert overflows, but reports them to the
caller.
-rw-r--r-- | runtime/doc/options.txt | 1 | ||||
-rw-r--r-- | src/nvim/buffer.c | 10 | ||||
-rw-r--r-- | src/nvim/charset.c | 39 | ||||
-rw-r--r-- | test/functional/eval/modeline_spec.lua | 19 |
4 files changed, 55 insertions, 14 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index fd2d5232ac..5a5999e64c 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -497,6 +497,7 @@ For example, to use a modeline only for Vim 7.0: To use a modeline for Vim after version 7.2: /* vim>702: set cole=2: */ ~ There can be no blanks between "vim" and the ":". +The modeline is ignored if {vers} does not fit in an integer. {Nvim} The number of lines that are checked can be set with the 'modelines' option. diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 5fb011885e..a573c20648 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -4509,7 +4509,7 @@ chk_modeline ( char_u *e; char_u *linecopy; /* local copy of any modeline found */ int prev; - int vers; + intmax_t vers; int end; int retval = OK; char_u *save_sourcing_name; @@ -4528,7 +4528,10 @@ chk_modeline ( e = s + 4; else e = s + 3; - vers = getdigits_int(&e); + if (getdigits_safe(&e, &vers) != OK) { + continue; + } + if (*e == ':' && (s[0] != 'V' || STRNCMP(skipwhite(e + 1), "set", 3) == 0) @@ -4536,8 +4539,9 @@ chk_modeline ( || (VIM_VERSION_100 >= vers && isdigit(s[3])) || (VIM_VERSION_100 < vers && s[3] == '<') || (VIM_VERSION_100 > vers && s[3] == '>') - || (VIM_VERSION_100 == vers && s[3] == '='))) + || (VIM_VERSION_100 == vers && s[3] == '='))) { break; + } } } prev = *s; diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 78f5d96fc7..61c5b10808 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1732,6 +1732,26 @@ char_u* skiptowhite_esc(char_u *p) { return p; } +/// Get a number from a string and skip over it, signalling overflows +/// +/// @param[out] pp A pointer to a pointer to char_u. +/// It will be advanced past the read number. +/// @param[out] nr Number read from the string. +/// +/// @return OK on success, FAIL on error/overflow +int getdigits_safe(char_u **pp, intmax_t *nr) +{ + errno = 0; + *nr = strtoimax((char *)(*pp), (char **)pp, 10); + + if ((*nr == INTMAX_MIN || *nr == INTMAX_MAX) + && errno == ERANGE) { + return FAIL; + } + + return OK; +} + /// Get a number from a string and skip over it. /// /// @param[out] pp A pointer to a pointer to char_u. @@ -1740,17 +1760,16 @@ char_u* skiptowhite_esc(char_u *p) { /// @return Number read from the string. intmax_t getdigits(char_u **pp) { - errno = 0; - intmax_t number = strtoimax((char *)*pp, (char **)pp, 10); - if (number == INTMAX_MAX || number == INTMAX_MIN) { - assert(errno != ERANGE); - } + intmax_t number; + int ret = getdigits_safe(pp, &number); + + (void)ret; // Avoid "unused variable" warning in Release build + assert(ret == OK); + return number; } -/// Get an int number from a string. -/// -/// A getdigits wrapper restricted to int values. +/// Get an int number from a string. Like getdigits(), but restricted to `int`. int getdigits_int(char_u **pp) { intmax_t number = getdigits(pp); @@ -1760,9 +1779,7 @@ int getdigits_int(char_u **pp) return (int)number; } -/// Get a long number from a string. -/// -/// A getdigits wrapper restricted to long values. +/// Get a long number from a string. Like getdigits(), but restricted to `long`. long getdigits_long(char_u **pp) { intmax_t number = getdigits(pp); diff --git a/test/functional/eval/modeline_spec.lua b/test/functional/eval/modeline_spec.lua new file mode 100644 index 0000000000..0be7210a76 --- /dev/null +++ b/test/functional/eval/modeline_spec.lua @@ -0,0 +1,19 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, execute, write_file = helpers.clear, helpers.execute, helpers.write_file +local eq, eval = helpers.eq, helpers.eval + +describe("modeline", function() + local tempfile = helpers.tmpname() + before_each(clear) + + after_each(function() + os.remove(tempfile) + end) + + it('does not crash with a large version number', function() + write_file(tempfile, 'vim100000000000000000000000') + execute('e! ' .. tempfile) + + eq(2, eval('1+1')) -- Still alive? + end) +end) |