Commit 29631cabd62f8a9790b5ea9c358fc6557fa17d2b

Authored by Jay Berkenbilt
1 parent 47a88683

Fix bash completion issue

Reported by Stephane Chazelas. Potentially sensitive arguments could
be leaked to the environment during completion computation.
completions/bash/qpdf
1   -eval $(/usr/bin/qpdf --completion-bash)
  1 +eval "$(/usr/bin/qpdf --completion-bash)"
... ...
completions/zsh/_qpdf
1 1 #compdef qpdf
2   -eval $(/usr/bin/qpdf --completion-zsh)
  2 +eval "$(/usr/bin/qpdf --completion-zsh)"
... ...
generate_auto_job
... ... @@ -351,6 +351,16 @@ class Main:
351 351 for k, v in hashes.items():
352 352 print(f'{k} {v}', file=f)
353 353  
  354 + @staticmethod
  355 + def quotes(long_text):
  356 + if '"(' in long_text or ')"' in long_text:
  357 + r_quote_open = 'R"/('
  358 + r_quote_close = ')/"'
  359 + else:
  360 + r_quote_open = 'R"('
  361 + r_quote_close = ')"'
  362 + return (r_quote_open, r_quote_close)
  363 +
354 364 def generate_doc(self, df, f, f_man):
355 365 """
356 366 Generates documentation and help-related functionalities for a given parser.
... ... @@ -498,8 +508,10 @@ class Main:
498 508 elif state == st_topic:
499 509 if append_long_text(line, topic):
500 510 self.all_topics.add(topic)
  511 + r_quote_open, r_quote_close = self.quotes(long_text)
501 512 print(f'ap.addHelpTopic("{topic}", "{short_text}",'
502   - f' R"({long_text})");', file=f)
  513 + f' {r_quote_open}{long_text}{r_quote_close});',
  514 + file=f)
503 515 print(f'.SH {topic.upper()} ({short_text})', file=f_man)
504 516 print(manify(long_text), file=f_man, end='')
505 517 help_lines += 1
... ... @@ -523,8 +535,11 @@ class Main:
523 535 f' lineno={lineno}')
524 536 if option not in self.help_options:
525 537 self.jdata[option[2:]]['help'] = short_text
  538 + r_quote_open, r_quote_close = self.quotes(long_text)
526 539 print(f'ap.addOptionHelp("{option}", "{topic}",'
527   - f' "{short_text}", R"({long_text})");', file=f)
  540 + f' "{short_text}",'
  541 + f' {r_quote_open}{long_text}{r_quote_close});',
  542 + file=f)
528 543 if last_option_topic != topic:
529 544 print('.PP\nRelated Options:', file=f_man)
530 545 last_option_topic = topic
... ...
job.sums
1 1 # Generated by generate_auto_job
2 2 CMakeLists.txt 18214e276670dc8beb2ab83f789c6d94941bc92b199b353f3943024cfd41d3bc
3   -generate_auto_job 280b75d5307c537385a75ec588493496cfb0bc754d48c34ca8c42bbc55dd717b
  3 +generate_auto_job 8e3175a515aa8837d8a01bba0346b04b3d777d70330ba5b7d52f691316054a34
4 4 include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4
5 5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42
6 6 include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a349e0cd4ae17ddd5
... ... @@ -9,12 +9,12 @@ include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa8
9 9 include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62
10 10 job.yml e136726a6c7e43736b1f75f4de347fa50baf5f38ed1da58647ce2e980751fb29
11 11 libqpdf/qpdf/auto_job_decl.hh 34ba07d3891c3e5cdd8712f991e508a0652c9db314c5d5bcdf4421b76e6f6e01
12   -libqpdf/qpdf/auto_job_help.hh bcfe600cc01447a7fae8661a8d101c7b15ce144bb06ba0beab656f3a655371d1
  12 +libqpdf/qpdf/auto_job_help.hh d0cca031e99f10caa3f4b70ea574b36b0af63d24de333e7d6f0bf835e959f0be
13 13 libqpdf/qpdf/auto_job_init.hh 02c526c37ad4051cac956ac7c12ae1d020517264f3f3d3beabb066ae2529e4bf
14 14 libqpdf/qpdf/auto_job_json_decl.hh 04965f6321e54b8b3b1dd2ca101d763a22ab44fa81c69e4b6fc0fd6bb7f50f92
15 15 libqpdf/qpdf/auto_job_json_init.hh b49378f00d521a9f3e0ce9086e30b082bc6ef8e43c845e2a3c99857b72448307
16 16 libqpdf/qpdf/auto_job_schema.hh f6a3e8b663714bba50b594f5e31437bbcb96ca4609d2c150c3bbc172e3b000fa
17 17 manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580
18   -manual/cli.rst 6fae28c9589bfde5b55260c95a7c64ad48688875f14f195129606405b32a04c6
19   -manual/qpdf.1 358dfe1bbeb49366d6dd17f74d883295344725b985183b8ca5e23226461654b3
  18 +manual/cli.rst b7bd5e34495d3f9156ff6242988dba73a2e5dce33d71f75ec1415514a3843f35
  19 +manual/qpdf.1 d5785d23e77b02a77180419d87787002dc244d82d586d56008ab603299f565fd
20 20 manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b
... ...
libqpdf/QPDFArgParser.cc
... ... @@ -183,13 +183,27 @@ QPDFArgParser::completionCommon(bool zsh)
183 183 }
184 184 }
185 185 if (zsh) {
186   - std::cout << "autoload -U +X bashcompinit && bashcompinit && ";
187   - }
188   - std::cout << "complete -o bashdefault -o default";
189   - if (!zsh) {
190   - std::cout << " -o nospace";
  186 + // FIXME: we assume progname doesn't contain single quote
  187 + // characters. 's in progname like in "/opt/joe's software/qpdf"
  188 + // should ideally be escaped as '\''. Unlikely to be a problem
  189 + // in practice. '...' is preferable over "..." as inside the
  190 + // latter more characters ("$`\) are a problem and it's
  191 + // virtually impossible to escape those in a locale-independent
  192 + // way.
  193 + std::cout << "autoload -U +X bashcompinit && bashcompinit &&"
  194 + << "complete -o bashdefault -o default -C '" << progname << "' " << m->whoami
  195 + << "\n";
  196 + } else {
  197 + // we need a function wrapper that discards arguments to avoid
  198 + // leaking sensitive information in the process argument list
  199 + // which is public on most systems. Here putting the code on one
  200 + // line as old versions of the documentation were instructing
  201 + // users to do eval $(qpdf --completion-bash) instead of the
  202 + // correct eval "$(qpdf --completion-bash)"
  203 + std::cout << "qpdf_completer() { '" << progname << "'; }; "
  204 + << "complete -o bashdefault -o default -o nospace -F qpdf_completer " << m->whoami
  205 + << "\n";
191 206 }
192   - std::cout << " -C \"" << progname << "\" " << m->whoami << '\n';
193 207 // Put output before error so calling from zsh works properly
194 208 std::string path = progname;
195 209 size_t slash = path.find('/');
... ... @@ -376,9 +390,19 @@ QPDFArgParser::checkCompletion()
376 390 if (p > m->bash_line.length()) {
377 391 p = m->bash_line.length();
378 392 }
379   - // Set bash_cur and bash_prev based on bash_line rather than relying on argv. This enables
380   - // us to use bashcompinit to get completion in zsh too since bashcompinit sets COMP_LINE and
381   - // COMP_POINT but doesn't invoke the command with options like bash does.
  393 + // Set bash_cur and bash_prev based on bash_line rather than relying on
  394 + // argv. Using argv is unsafe as process argument lists are public on
  395 + // most systems. zsh doesn't pass information there, and we actively
  396 + // discard them for bash with our qpdf_completer to avoid that
  397 + // information disclosure vulnerability. Both bash and zsh set
  398 + // COMP_LINE and COMP_POINT which we can rely on instead.
  399 + //
  400 + // FIXME. For both bash and zsh, COMP_POINT is an offset in terms of
  401 + // *characters*, with characters decoded as per the shell's own locale.
  402 + // Here we interpret it as a *byte* offset which means it won't work
  403 + // properly if there are multibyte characters to the left of the
  404 + // cursor, but saves us having to decode the command line (which is hard
  405 + // to do in the same way the shell does in all cases).
382 406  
383 407 // p is equal to length of the string. Walk backwards looking for the first separator.
384 408 // bash_cur is everything after the last separator, possibly empty.
... ...
libqpdf/qpdf/auto_job_help.hh
... ... @@ -45,11 +45,11 @@ ap.addHelpTopic(&quot;exit-status&quot;, &quot;meanings of qpdf&#39;s exit codes&quot;, R&quot;(Meaning of ex
45 45 ap.addOptionHelp("--warning-exit-0", "exit-status", "exit 0 even with warnings", R"(Use exit status 0 instead of 3 when warnings are present. When
46 46 combined with --no-warn, warnings are completely ignored.
47 47 )");
48   -ap.addHelpTopic("completion", "shell completion", R"(Shell completion is supported with bash and zsh. Use
49   -eval $(qpdf --completion-bash) or eval $(qpdf --completion-zsh)
  48 +ap.addHelpTopic("completion", "shell completion", R"/(Shell completion is supported with bash and zsh. Use
  49 +eval "$(qpdf --completion-bash)" or eval "$(qpdf --completion-zsh)"
50 50 to enable. The QPDF_EXECUTABLE environment variable overrides the
51 51 path to qpdf that these commands output.
52   -)");
  52 +)/");
53 53 ap.addOptionHelp("--completion-bash", "completion", "enable bash completion", R"(Output a command that enables bash completion
54 54 )");
55 55 ap.addOptionHelp("--completion-zsh", "completion", "enable zsh completion", R"(Output a command that enables zsh completion
... ...
manual/cli.rst
... ... @@ -244,14 +244,14 @@ Shell Completion
244 244 .. help-topic completion: shell completion
245 245  
246 246 Shell completion is supported with bash and zsh. Use
247   - eval $(qpdf --completion-bash) or eval $(qpdf --completion-zsh)
  247 + eval "$(qpdf --completion-bash)" or eval "$(qpdf --completion-zsh)"
248 248 to enable. The QPDF_EXECUTABLE environment variable overrides the
249 249 path to qpdf that these commands output.
250 250  
251 251 :command:`qpdf` provides its own completion support for zsh and bash.
252   -You can enable bash completion with :command:`eval $(qpdf
253   ---completion-bash)` and zsh completion with :command:`eval $(qpdf
254   ---completion-zsh)`. If :command:`qpdf` is not in your path, you should
  252 +You can enable bash completion with :command:`eval "$(qpdf
  253 +--completion-bash)"` and zsh completion with :command:`eval "$(qpdf
  254 +--completion-zsh)"`. If :command:`qpdf` is not in your path, you should
255 255 use an absolute path to qpdf in the above invocation. If you invoke it
256 256 with a relative path, it will warn you, and the completion won't work
257 257 if you're in a different directory.
... ...
manual/qpdf.1
... ... @@ -78,7 +78,7 @@ Use exit status 0 instead of 3 when warnings are present. When
78 78 combined with --no-warn, warnings are completely ignored.
79 79 .SH COMPLETION (shell completion)
80 80 Shell completion is supported with bash and zsh. Use
81   -eval $(qpdf --completion-bash) or eval $(qpdf --completion-zsh)
  81 +eval "$(qpdf --completion-bash)" or eval "$(qpdf --completion-zsh)"
82 82 to enable. The QPDF_EXECUTABLE environment variable overrides the
83 83 path to qpdf that these commands output.
84 84 .PP
... ...
manual/release-notes.rst
... ... @@ -55,12 +55,15 @@ more detail.
55 55  
56 56 - CLI Enhancements
57 57  
58   - - Disallow option :qpdf:ref:`--deterministic-id` to be used together
59   - with the incompatible options :qpdf:ref:`--encrypt` or
60   - :qpdf:ref:`--copy-encryption`.
  58 + - Disallow option :qpdf:ref:`--deterministic-id` to be used together
  59 + with the incompatible options :qpdf:ref:`--encrypt` or
  60 + :qpdf:ref:`--copy-encryption`.
61 61  
62   - - Option :qpdf:ref:`--check` now includes additional basic checks of the
63   - AcroForm, Dests, Outlines, and PageLabels structures.
  62 + - Option :qpdf:ref:`--check` now includes additional basic checks of the
  63 + AcroForm, Dests, Outlines, and PageLabels structures.
  64 +
  65 + - Fix completion scripts and handling to avoid leaking arguments
  66 + into the environment during completion.
64 67  
65 68 - Other enhancements
66 69  
... ...