Commit df38fe8e48528c82e16e7bcced38eeab5bcf9d7c

Authored by Jay Berkenbilt
1 parent 3cacb27a

Fix string bounds checking in completion code (fixes #441)

ChangeLog
  1 +2021-05-12 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Bug fix: ensure we don't overflow any string bounds while
  4 + handling completion, even when we are given bogus input values.
  5 + Fixes #441.
  6 +
1 7 2021-05-09 Jay Berkenbilt <ejb@ql.org>
2 8  
3 9 * Improve performance of preservation of object streams by
... ...
qpdf/qpdf.cc
... ... @@ -3469,16 +3469,27 @@ ArgParser::checkCompletion()
3469 3469 {
3470 3470 // See if we're being invoked from bash completion.
3471 3471 std::string bash_point_env;
3472   - if (QUtil::get_env("COMP_LINE", &bash_line) &&
3473   - QUtil::get_env("COMP_POINT", &bash_point_env))
  3472 + // On Windows with mingw, there have been times when there appears
  3473 + // to be no way to distinguish between an empty environment
  3474 + // variable and an unset variable. There are also conditions under
  3475 + // which bash doesn't set COMP_LINE. Therefore, enter this logic
  3476 + // if either COMP_LINE or COMP_POINT are set. They will both be
  3477 + // set together under ordinary circumstances.
  3478 + bool got_line = QUtil::get_env("COMP_LINE", &bash_line);
  3479 + bool got_point = QUtil::get_env("COMP_POINT", &bash_point_env);
  3480 + if (got_line || got_point)
3474 3481 {
3475 3482 size_t p = QUtil::string_to_uint(bash_point_env.c_str());
3476   - if ((p > 0) && (p <= bash_line.length()))
  3483 + if (p < bash_line.length())
3477 3484 {
3478 3485 // Truncate the line. We ignore everything at or after the
3479 3486 // cursor for completion purposes.
3480 3487 bash_line = bash_line.substr(0, p);
3481 3488 }
  3489 + if (p > bash_line.length())
  3490 + {
  3491 + p = bash_line.length();
  3492 + }
3482 3493 // Set bash_cur and bash_prev based on bash_line rather than
3483 3494 // relying on argv. This enables us to use bashcompinit to get
3484 3495 // completion in zsh too since bashcompinit sets COMP_LINE and
... ... @@ -3489,8 +3500,9 @@ ArgParser::checkCompletion()
3489 3500 // for the first separator. bash_cur is everything after the
3490 3501 // last separator, possibly empty.
3491 3502 char sep(0);
3492   - while (--p > 0)
  3503 + while (p > 0)
3493 3504 {
  3505 + --p;
3494 3506 char ch = bash_line.at(p);
3495 3507 if ((ch == ' ') || (ch == '=') || (ch == ':'))
3496 3508 {
... ... @@ -3498,7 +3510,10 @@ ArgParser::checkCompletion()
3498 3510 break;
3499 3511 }
3500 3512 }
3501   - bash_cur = bash_line.substr(1+p, std::string::npos);
  3513 + if (1+p <= bash_line.length())
  3514 + {
  3515 + bash_cur = bash_line.substr(1+p, std::string::npos);
  3516 + }
3502 3517 if ((sep == ':') || (sep == '='))
3503 3518 {
3504 3519 // Bash sets prev to the non-space separator if any.
... ... @@ -3512,8 +3527,9 @@ ArgParser::checkCompletion()
3512 3527 // Go back to the last separator and set prev based on
3513 3528 // that.
3514 3529 size_t p1 = p;
3515   - while (--p1 > 0)
  3530 + while (p1 > 0)
3516 3531 {
  3532 + --p1;
3517 3533 char ch = bash_line.at(p1);
3518 3534 if ((ch == ' ') || (ch == ':') || (ch == '='))
3519 3535 {
... ...
qpdf/qtest/qpdf.test
... ... @@ -86,6 +86,10 @@ $td-&gt;runtest(&quot;UTF-16 encoding errors&quot;,
86 86 $td->NORMALIZE_NEWLINES);
87 87  
88 88 my @completion_tests = (
  89 + ['', 0, 'bad-input-1'],
  90 + ['', 1, 'bad-input-2'],
  91 + ['', 2, 'bad-input-3'],
  92 + ['qpdf', 2, 'bad-input-4'],
89 93 ['qpdf ', undef, 'top'],
90 94 ['qpdf -', undef, 'top-arg'],
91 95 ['qpdf --enc', undef, 'enc'],
... ... @@ -5229,8 +5233,13 @@ sub bash_completion
5229 5233 $point = length($line);
5230 5234 }
5231 5235 my $before_point = substr($line, 0, $point);
5232   - $before_point =~ m/^(.*)([ =])([^= ]*)$/ or die;
5233   - my ($first, $sep, $cur) = ($1, $2, $3);
  5236 + my $first = '';
  5237 + my $sep = '';
  5238 + my $cur = '';
  5239 + if ($before_point =~ m/^(.*)([ =])([^= ]*)$/)
  5240 + {
  5241 + ($first, $sep, $cur) = ($1, $2, $3);
  5242 + }
5234 5243 my $prev = ($sep eq '=' ? $sep : $first);
5235 5244 $prev =~ s/.* (\S+)$/$1/;
5236 5245 my $this = $first;
... ...
qpdf/qtest/qpdf/completion-bad-input-1.out 0 → 100644
  1 +!
... ...
qpdf/qtest/qpdf/completion-bad-input-2.out 0 → 100644
  1 +!
... ...
qpdf/qtest/qpdf/completion-bad-input-3.out 0 → 100644
  1 +!
... ...
qpdf/qtest/qpdf/completion-bad-input-4.out 0 → 100644
  1 +!
... ...