Commit df38fe8e48528c82e16e7bcced38eeab5bcf9d7c
1 parent
3cacb27a
Fix string bounds checking in completion code (fixes #441)
Showing
7 changed files
with
43 additions
and
8 deletions
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 | 2021-05-09 Jay Berkenbilt <ejb@ql.org> | 7 | 2021-05-09 Jay Berkenbilt <ejb@ql.org> |
| 2 | 8 | ||
| 3 | * Improve performance of preservation of object streams by | 9 | * Improve performance of preservation of object streams by |
qpdf/qpdf.cc
| @@ -3469,16 +3469,27 @@ ArgParser::checkCompletion() | @@ -3469,16 +3469,27 @@ ArgParser::checkCompletion() | ||
| 3469 | { | 3469 | { |
| 3470 | // See if we're being invoked from bash completion. | 3470 | // See if we're being invoked from bash completion. |
| 3471 | std::string bash_point_env; | 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 | size_t p = QUtil::string_to_uint(bash_point_env.c_str()); | 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 | // Truncate the line. We ignore everything at or after the | 3485 | // Truncate the line. We ignore everything at or after the |
| 3479 | // cursor for completion purposes. | 3486 | // cursor for completion purposes. |
| 3480 | bash_line = bash_line.substr(0, p); | 3487 | bash_line = bash_line.substr(0, p); |
| 3481 | } | 3488 | } |
| 3489 | + if (p > bash_line.length()) | ||
| 3490 | + { | ||
| 3491 | + p = bash_line.length(); | ||
| 3492 | + } | ||
| 3482 | // Set bash_cur and bash_prev based on bash_line rather than | 3493 | // Set bash_cur and bash_prev based on bash_line rather than |
| 3483 | // relying on argv. This enables us to use bashcompinit to get | 3494 | // relying on argv. This enables us to use bashcompinit to get |
| 3484 | // completion in zsh too since bashcompinit sets COMP_LINE and | 3495 | // completion in zsh too since bashcompinit sets COMP_LINE and |
| @@ -3489,8 +3500,9 @@ ArgParser::checkCompletion() | @@ -3489,8 +3500,9 @@ ArgParser::checkCompletion() | ||
| 3489 | // for the first separator. bash_cur is everything after the | 3500 | // for the first separator. bash_cur is everything after the |
| 3490 | // last separator, possibly empty. | 3501 | // last separator, possibly empty. |
| 3491 | char sep(0); | 3502 | char sep(0); |
| 3492 | - while (--p > 0) | 3503 | + while (p > 0) |
| 3493 | { | 3504 | { |
| 3505 | + --p; | ||
| 3494 | char ch = bash_line.at(p); | 3506 | char ch = bash_line.at(p); |
| 3495 | if ((ch == ' ') || (ch == '=') || (ch == ':')) | 3507 | if ((ch == ' ') || (ch == '=') || (ch == ':')) |
| 3496 | { | 3508 | { |
| @@ -3498,7 +3510,10 @@ ArgParser::checkCompletion() | @@ -3498,7 +3510,10 @@ ArgParser::checkCompletion() | ||
| 3498 | break; | 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 | if ((sep == ':') || (sep == '=')) | 3517 | if ((sep == ':') || (sep == '=')) |
| 3503 | { | 3518 | { |
| 3504 | // Bash sets prev to the non-space separator if any. | 3519 | // Bash sets prev to the non-space separator if any. |
| @@ -3512,8 +3527,9 @@ ArgParser::checkCompletion() | @@ -3512,8 +3527,9 @@ ArgParser::checkCompletion() | ||
| 3512 | // Go back to the last separator and set prev based on | 3527 | // Go back to the last separator and set prev based on |
| 3513 | // that. | 3528 | // that. |
| 3514 | size_t p1 = p; | 3529 | size_t p1 = p; |
| 3515 | - while (--p1 > 0) | 3530 | + while (p1 > 0) |
| 3516 | { | 3531 | { |
| 3532 | + --p1; | ||
| 3517 | char ch = bash_line.at(p1); | 3533 | char ch = bash_line.at(p1); |
| 3518 | if ((ch == ' ') || (ch == ':') || (ch == '=')) | 3534 | if ((ch == ' ') || (ch == ':') || (ch == '=')) |
| 3519 | { | 3535 | { |
qpdf/qtest/qpdf.test
| @@ -86,6 +86,10 @@ $td->runtest("UTF-16 encoding errors", | @@ -86,6 +86,10 @@ $td->runtest("UTF-16 encoding errors", | ||
| 86 | $td->NORMALIZE_NEWLINES); | 86 | $td->NORMALIZE_NEWLINES); |
| 87 | 87 | ||
| 88 | my @completion_tests = ( | 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 | ['qpdf ', undef, 'top'], | 93 | ['qpdf ', undef, 'top'], |
| 90 | ['qpdf -', undef, 'top-arg'], | 94 | ['qpdf -', undef, 'top-arg'], |
| 91 | ['qpdf --enc', undef, 'enc'], | 95 | ['qpdf --enc', undef, 'enc'], |
| @@ -5229,8 +5233,13 @@ sub bash_completion | @@ -5229,8 +5233,13 @@ sub bash_completion | ||
| 5229 | $point = length($line); | 5233 | $point = length($line); |
| 5230 | } | 5234 | } |
| 5231 | my $before_point = substr($line, 0, $point); | 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 | my $prev = ($sep eq '=' ? $sep : $first); | 5243 | my $prev = ($sep eq '=' ? $sep : $first); |
| 5235 | $prev =~ s/.* (\S+)$/$1/; | 5244 | $prev =~ s/.* (\S+)$/$1/; |
| 5236 | my $this = $first; | 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 | +! |