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 | 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->runtest("UTF-16 encoding errors", |
| 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 | +! | ... | ... |