mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
				
					committed by
					
						
						Justin M. Keyes
					
				
			
			
				
	
			
			
			
						parent
						
							0f32088ea2
						
					
				
				
					commit
					2a6c5bb0c4
				
			@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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,10 +4539,11 @@ 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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								test/functional/eval/modeline_spec.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								test/functional/eval/modeline_spec.lua
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
			
		||||
		Reference in New Issue
	
	Block a user