Commit 8f455ffa6a177a61e12ad087c2cf916b6209c5da

Authored by m-holger
Committed by GitHub
2 parents a3fca12d 898c2943

Merge pull request #1590 from m-holger/i1475

Add a limit on the maximum number of filters allowed when filtering streams.
include/qpdf/Constants.h
... ... @@ -287,6 +287,9 @@ enum qpdf_param_e {
287 287 qpdf_p_parser_max_container_size,
288 288 qpdf_p_parser_max_container_size_damaged,
289 289  
  290 + /* stream and filter limits */
  291 + qpdf_p_max_stream_filters = 0x14000,
  292 +
290 293 /* next section = 0x20000 */
291 294 qpdf_enum_max = 0x7fffffff,
292 295 };
... ...
include/qpdf/auto_job_c_global.hh
... ... @@ -10,3 +10,4 @@ QPDF_DLL GlobalConfig* parserMaxContainerSize(std::string const& parameter);
10 10 QPDF_DLL GlobalConfig* parserMaxContainerSizeDamaged(std::string const& parameter);
11 11 QPDF_DLL GlobalConfig* parserMaxErrors(std::string const& parameter);
12 12 QPDF_DLL GlobalConfig* parserMaxNesting(std::string const& parameter);
  13 +QPDF_DLL GlobalConfig* maxStreamFilters(std::string const& parameter);
... ...
include/qpdf/global.hh
... ... @@ -56,7 +56,7 @@ namespace qpdf::global
56 56  
57 57 /// @brief Retrieves the number of limit errors.
58 58 ///
59   - /// Returns the number a global limit was exceeded. This item is read only.
  59 + /// Returns the number of times a global limit was exceeded. This item is read only.
60 60 ///
61 61 /// @return The number of limit errors.
62 62 ///
... ... @@ -229,6 +229,34 @@ namespace qpdf::global
229 229 {
230 230 set_uint32(qpdf_p_parser_max_container_size_damaged, value);
231 231 }
  232 +
  233 + /// @brief Retrieves the maximum number of filters allowed when filtering streams.
  234 + ///
  235 + /// An excessive number of stream filters is usually a sign that a file is damaged or
  236 + /// specially constructed. If the maximum is exceeded for a stream the stream is treated as
  237 + /// unfilterable. The default maximum is 25.
  238 + ///
  239 + /// @return The maximum number of filters allowed when filtering streams.
  240 + ///
  241 + /// @since 12.3
  242 + uint32_t inline max_stream_filters()
  243 + {
  244 + return get_uint32(qpdf_p_max_stream_filters);
  245 + }
  246 +
  247 + /// @brief Sets the maximum number of filters allowed when filtering streams.
  248 + ///
  249 + /// An excessive number of stream filters is usually a sign that a file is damaged or
  250 + /// specially constructed. If the maximum is exceeded for a stream the stream is treated as
  251 + /// unfilterable. The default maximum is 25.
  252 + ///
  253 + /// @param value The maximum number of filters allowed when filtering streams to set.
  254 + ///
  255 + /// @since 12.3
  256 + void inline max_stream_filters(uint32_t value)
  257 + {
  258 + set_uint32(qpdf_p_max_stream_filters, value);
  259 + }
232 260 } // namespace limits
233 261  
234 262 } // namespace qpdf::global
... ...
job.sums
... ... @@ -4,18 +4,18 @@ generate_auto_job 8e3175a515aa8837d8a01bba0346b04b3d777d70330ba5b7d52f691316054a
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
7   -include/qpdf/auto_job_c_global.hh f1dc365206d033a0d6b19b6e561cc244fbd5b49a8d9604b5b646a5fd92895a5a
  7 +include/qpdf/auto_job_c_global.hh 7df0ff87d18d7fa6d57437960377509420b6b6eb9527b534996f86d3bd7a0ddc
8 8 include/qpdf/auto_job_c_main.hh b865eb827356554763bb8349eadfcbc5cb260f80e025a5e229467c525007356d
9 9 include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa86d83df31051d82506
10 10 include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62
11   -job.yml 131922d22086d9f4710743e18229cc1e956268197bcae8e1aae30f3be42877be
  11 +job.yml fa98c8444c8a22a89aeeb76670aa5919aa7a86ebfac2eb45018602fbc7e45b79
12 12 libqpdf/qpdf/auto_job_decl.hh d612a02839e4f20a80e1c6a3ba09c17187fccddc3581ec7ebb1e3919ffd6801d
13   -libqpdf/qpdf/auto_job_help.hh 00ac90c621b6c0529d7bad9ea596f57595517901c8d33f49d2812fbea52dfb41
14   -libqpdf/qpdf/auto_job_init.hh 889dde948e0ab53616584976d9520ab7ab3773c787d241f8a107f5e2f9f2112f
  13 +libqpdf/qpdf/auto_job_help.hh 7503b1083c952ace12976857c8ece4e1af67788af9f827fb4248bd22329f93cd
  14 +libqpdf/qpdf/auto_job_init.hh 10a697528d4cae1ac566ee7612f62e611190b3c10c0021862a77fa7e4f330570
15 15 libqpdf/qpdf/auto_job_json_decl.hh 7dbb83ddadcea39bfd1faa4ca061e1e3c3134d693b8ae634b463e7e19dc8bd0a
16   -libqpdf/qpdf/auto_job_json_init.hh 3c5f3d07a85e89dd7ecd79342c18e1f0ad580fc57758abb434aa9c9ae277c01e
17   -libqpdf/qpdf/auto_job_schema.hh eb21c99d3a4dc40b333fd1b19d5de52f8813c74a1d4ca830ea4c3311c120d63e
  16 +libqpdf/qpdf/auto_job_json_init.hh e9cacbcb78ca250a962c226a935067ef9b76f5485bae7e5302eea0a1a8e2ff65
  17 +libqpdf/qpdf/auto_job_schema.hh 2b974a436c5b4d03fb38258d6213f993cfa9f673834cebe754b4c7ad657481c9
18 18 manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580
19   -manual/cli.rst 08e9e7a18d2b0d05102a072f82eabf9ede6bfb1fb797be307ea680eed93ea60f
20   -manual/qpdf.1 19a45f8de6b7c0584fe4395c4ae98b92147a2875e45dbdf729c70e644ccca295
  19 +manual/cli.rst 0b0f6a1d8ec523751d91999586bca1356abd8f17e207bc0139ce5d7dfd64fdb4
  20 +manual/qpdf.1 c1d6e58e37aed1b8d434b37edd1837b7261c9933b09d64bf3915dc3f35d6cccb
21 21 manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b
... ...
... ... @@ -95,6 +95,7 @@ options:
95 95 parser-max-container-size-damaged: level
96 96 parser-max-errors: level
97 97 parser-max-nesting: level
  98 + max-stream-filters: level
98 99 - table: main
99 100 config: c_main
100 101 manual:
... ... @@ -419,6 +420,7 @@ json:
419 420 parser-max-container-size-damaged:
420 421 parser-max-errors:
421 422 parser-max-nesting:
  423 + max-stream-filters:
422 424 # other options
423 425 update-from-json:
424 426 allow-weak-crypto:
... ...
libqpdf/QPDFJob_config.cc
... ... @@ -1216,6 +1216,13 @@ QPDFJob::GlobalConfig::parserMaxNesting(const std::string& parameter)
1216 1216 return this;
1217 1217 }
1218 1218  
  1219 +QPDFJob::GlobalConfig*
  1220 +QPDFJob::GlobalConfig::maxStreamFilters(const std::string& parameter)
  1221 +{
  1222 + global::Limits::max_stream_filters(to_uint32("max-stream-filters", parameter));
  1223 + return this;
  1224 +}
  1225 +
1219 1226 QPDFJob::Config*
1220 1227 QPDFJob::Config::setPageLabels(const std::vector<std::string>& specs)
1221 1228 {
... ...
libqpdf/QPDF_Stream.cc
... ... @@ -513,6 +513,13 @@ Stream::filterable(
513 513 // No filters
514 514 return true;
515 515 }
  516 + if (filter_obj.size() > global::Limits::max_stream_filters()) {
  517 + global::Limits::error();
  518 + warn(
  519 + "limits error(max-stream-filters): too many filters for stream; treating stream as "
  520 + "not filterable");
  521 + return false;
  522 + }
516 523 if (filter_obj.isName()) {
517 524 // One filter
518 525 auto ff = s->filter_factory(filter_obj.getName());
... ...
libqpdf/global.cc
... ... @@ -28,6 +28,9 @@ Limits::disable_defaults()
28 28 if (!l.parser_max_container_size_damaged_set_) {
29 29 l.parser_max_container_size_damaged_ = std::numeric_limits<uint32_t>::max();
30 30 }
  31 + if (!l.max_stream_filters_set_) {
  32 + l.max_stream_filters_ = std::numeric_limits<uint32_t>::max();
  33 + }
31 34 }
32 35  
33 36 qpdf_result_e
... ... @@ -56,6 +59,9 @@ qpdf_global_get_uint32(qpdf_param_e param, uint32_t* value)
56 59 case qpdf_p_parser_max_container_size_damaged:
57 60 *value = Limits::parser_max_container_size(true);
58 61 return qpdf_r_ok;
  62 + case qpdf_p_max_stream_filters:
  63 + *value = Limits::max_stream_filters();
  64 + return qpdf_r_ok;
59 65 default:
60 66 return qpdf_r_bad_parameter;
61 67 }
... ... @@ -83,6 +89,9 @@ qpdf_global_set_uint32(qpdf_param_e param, uint32_t value)
83 89 case qpdf_p_parser_max_container_size_damaged:
84 90 Limits::parser_max_container_size(true, value);
85 91 return qpdf_r_ok;
  92 + case qpdf_p_max_stream_filters:
  93 + Limits::max_stream_filters(value);
  94 + return qpdf_r_ok;
86 95 default:
87 96 return qpdf_r_bad_parameter;
88 97 }
... ...
libqpdf/qpdf/auto_job_help.hh
... ... @@ -1044,6 +1044,13 @@ See also --parser-max-container-size.
1044 1044 }
1045 1045 static void add_help_9(QPDFArgParser& ap)
1046 1046 {
  1047 +ap.addOptionHelp("--max-stream-filters", "global", "set the maximum number of filters allowed when filtering streams", R"(--max-stream-filters=n
  1048 +
  1049 +An excessive number of stream filters is usually a sign that a file
  1050 +is damaged or specially constructed. If the maximum is exceeded for
  1051 +a stream the stream is treated as unfilterable.
  1052 +The default limit is 25.
  1053 +)");
1047 1054 ap.addHelpTopic("testing", "options for testing or debugging", R"(The options below are useful when writing automated test code that
1048 1055 includes files created by qpdf or when testing qpdf itself.
1049 1056 )");
... ...
libqpdf/qpdf/auto_job_init.hh
... ... @@ -40,6 +40,7 @@ this-&gt;ap.addRequiredParameter(&quot;parser-max-container-size&quot;, [this](std::string co
40 40 this->ap.addRequiredParameter("parser-max-container-size-damaged", [this](std::string const& x){c_global->parserMaxContainerSizeDamaged(x);}, "level");
41 41 this->ap.addRequiredParameter("parser-max-errors", [this](std::string const& x){c_global->parserMaxErrors(x);}, "level");
42 42 this->ap.addRequiredParameter("parser-max-nesting", [this](std::string const& x){c_global->parserMaxNesting(x);}, "level");
  43 +this->ap.addRequiredParameter("max-stream-filters", [this](std::string const& x){c_global->maxStreamFilters(x);}, "level");
43 44 this->ap.selectMainOptionTable();
44 45 this->ap.addPositional(p(&ArgParser::argPositional));
45 46 this->ap.addBare("add-attachment", b(&ArgParser::argAddAttachment));
... ...
libqpdf/qpdf/auto_job_json_init.hh
... ... @@ -289,6 +289,9 @@ popHandler(); // key: parserMaxErrors
289 289 pushKey("parserMaxNesting");
290 290 addParameter([this](std::string const& p) { c_global->parserMaxNesting(p); });
291 291 popHandler(); // key: parserMaxNesting
  292 +pushKey("maxStreamFilters");
  293 +addParameter([this](std::string const& p) { c_global->maxStreamFilters(p); });
  294 +popHandler(); // key: maxStreamFilters
292 295 popHandler(); // key: global
293 296 pushKey("updateFromJson");
294 297 addParameter([this](std::string const& p) { c_main->updateFromJson(p); });
... ...
libqpdf/qpdf/auto_job_schema.hh
... ... @@ -95,7 +95,8 @@ static constexpr char const* JOB_SCHEMA_DATA = R&quot;({
95 95 "parserMaxContainerSize": "set the maximum container size while parsing",
96 96 "parserMaxContainerSizeDamaged": "set the maximum container size while parsing damaged files",
97 97 "parserMaxErrors": "set the maximum number of errors while parsing",
98   - "parserMaxNesting": "set the maximum nesting level while parsing objects"
  98 + "parserMaxNesting": "set the maximum nesting level while parsing objects",
  99 + "maxStreamFilters": "set the maximum number of filters allowed when filtering streams"
99 100 },
100 101 "updateFromJson": "update a PDF from qpdf JSON",
101 102 "allowWeakCrypto": "allow insecure cryptographic algorithms",
... ...
libqpdf/qpdf/global_private.hh
... ... @@ -48,6 +48,19 @@ namespace qpdf::global
48 48  
49 49 static void parser_max_container_size(bool damaged, uint32_t value);
50 50  
  51 + static uint32_t const&
  52 + max_stream_filters()
  53 + {
  54 + return l.max_stream_filters_;
  55 + }
  56 +
  57 + static void
  58 + max_stream_filters(uint32_t value)
  59 + {
  60 + l.max_stream_filters_set_ = true;
  61 + l.max_stream_filters_ = value;
  62 + }
  63 +
51 64 /// Record a limit error.
52 65 static void
53 66 error()
... ... @@ -79,6 +92,8 @@ namespace qpdf::global
79 92 uint32_t parser_max_container_size_{std::numeric_limits<uint32_t>::max()};
80 93 uint32_t parser_max_container_size_damaged_{5'000};
81 94 bool parser_max_container_size_damaged_set_{false};
  95 + uint32_t max_stream_filters_{25};
  96 + bool max_stream_filters_set_{false};
82 97 };
83 98  
84 99 class Options
... ...
libtests/objects.cc
... ... @@ -4,6 +4,7 @@
4 4  
5 5 #include <qpdf/QPDF.hh>
6 6  
  7 +#include <qpdf/Pl_Discard.hh>
7 8 #include <qpdf/QIntC.hh>
8 9 #include <qpdf/QPDFJob.hh>
9 10 #include <qpdf/QPDFObjectHandle_private.hh>
... ... @@ -167,6 +168,7 @@ test_2(QPDF&amp; pdf, char const* arg2)
167 168 assert(parser_max_errors() == 15);
168 169 assert(parser_max_container_size() == std::numeric_limits<uint32_t>::max());
169 170 assert(parser_max_container_size_damaged() == 5'000);
  171 + assert(max_stream_filters() == 25);
170 172 assert(default_limits());
171 173  
172 174 // Test disabling optional default limits
... ... @@ -175,6 +177,7 @@ test_2(QPDF&amp; pdf, char const* arg2)
175 177 assert(parser_max_errors() == 0);
176 178 assert(parser_max_container_size() == std::numeric_limits<uint32_t>::max());
177 179 assert(parser_max_container_size_damaged() == std::numeric_limits<uint32_t>::max());
  180 + assert(max_stream_filters() == std::numeric_limits<uint32_t>::max());
178 181 assert(!default_limits());
179 182  
180 183 // Check disabling default limits is irreversible
... ... @@ -186,11 +189,13 @@ test_2(QPDF&amp; pdf, char const* arg2)
186 189 parser_max_errors(12);
187 190 parser_max_container_size(13);
188 191 parser_max_container_size_damaged(14);
  192 + max_stream_filters(15);
189 193  
190 194 assert(parser_max_nesting() == 11);
191 195 assert(parser_max_errors() == 12);
192 196 assert(parser_max_container_size() == 13);
193 197 assert(parser_max_container_size_damaged() == 14);
  198 + assert(max_stream_filters() == 15);
194 199  
195 200 // Check disabling default limits does not override explicit limits
196 201 default_limits(false);
... ... @@ -198,6 +203,7 @@ test_2(QPDF&amp; pdf, char const* arg2)
198 203 assert(parser_max_errors() == 12);
199 204 assert(parser_max_container_size() == 13);
200 205 assert(parser_max_container_size_damaged() == 14);
  206 + assert(max_stream_filters() == 15);
201 207  
202 208 // Test parameter checking
203 209 QUtil::handle_result_code(qpdf_r_ok, "");
... ... @@ -239,6 +245,19 @@ test_2(QPDF&amp; pdf, char const* arg2)
239 245 }
240 246 assert(qpdf::global::limit_errors() == 2);
241 247  
  248 + // Test max_stream_filters
  249 + QPDF qpdf;
  250 + qpdf.emptyPDF();
  251 + auto s = qpdf.newStream("\x01\x01\x01A");
  252 + s.getDict().replace("/Filter", Array({Name("/RL"), Name("/RL"), Name("/RL")}));
  253 + Pl_Discard p;
  254 + auto x = s.pipeStreamData(&p, 0, qpdf_dl_all, true);
  255 + assert(x);
  256 + max_stream_filters(2);
  257 + assert(!s.pipeStreamData(&p, 0, qpdf_dl_all, true));
  258 + max_stream_filters(3);
  259 + assert(s.pipeStreamData(&p, 0, qpdf_dl_all, true));
  260 +
242 261 // Test global settings using the QPDFJob interface
243 262 QPDFJob j;
244 263 j.config()
... ... @@ -248,14 +267,16 @@ test_2(QPDF&amp; pdf, char const* arg2)
248 267 ->parserMaxErrors("112")
249 268 ->parserMaxContainerSize("113")
250 269 ->parserMaxContainerSizeDamaged("114")
  270 + ->maxStreamFilters("115")
251 271 ->noDefaultLimits()
252 272 ->endGlobal()
253 273 ->outputFile("a.pdf");
254   - auto qpdf = j.createQPDF();
  274 + auto qpdf_uptr = j.createQPDF();
255 275 assert(parser_max_nesting() == 111);
256 276 assert(parser_max_errors() == 112);
257 277 assert(parser_max_container_size() == 113);
258 278 assert(parser_max_container_size_damaged() == 114);
  279 + assert(max_stream_filters() == 115);
259 280 assert(!default_limits());
260 281  
261 282 // Test global settings using the JobJSON
... ... @@ -268,16 +289,18 @@ test_2(QPDF&amp; pdf, char const* arg2)
268 289 "parserMaxErrors": "212",
269 290 "parserMaxContainerSize": "213",
270 291 "parserMaxContainerSizeDamaged": "214",
  292 + "maxStreamFilters": "215",
271 293 "noDefaultLimits": ""
272 294 },
273 295 "outputFile": "a.pdf"
274 296 }
275 297 )");
276   - qpdf = j.createQPDF();
  298 + qpdf_uptr = jj.createQPDF();
277 299 assert(parser_max_nesting() == 211);
278 300 assert(parser_max_errors() == 212);
279 301 assert(parser_max_container_size() == 213);
280 302 assert(parser_max_container_size_damaged() == 214);
  303 + assert(max_stream_filters() == 215);
281 304 assert(!default_limits());
282 305 }
283 306  
... ...
libtests/qtest/objects.test
... ... @@ -20,12 +20,12 @@ $td-&gt;runtest(&quot;integer type checks&quot;,
20 20  
21 21 $td->runtest("dictionary checks",
22 22 {$td->COMMAND => "objects 1 -"},
23   - {$td->STRING => => "test 1 done\n", $td->EXIT_STATUS => 0},
  23 + {$td->STRING => "test 1 done\n", $td->EXIT_STATUS => 0},
24 24 $td->NORMALIZE_NEWLINES);
25 25  
26   -$td->runtest("global object limits",
  26 +$td->runtest("global limits",
27 27 {$td->COMMAND => "objects 2 -"},
28   - {$td->STRING => => "test 2 done\n", $td->EXIT_STATUS => 0},
  28 + {$td->FILE => "test2.out", $td->EXIT_STATUS => 0},
29 29 $td->NORMALIZE_NEWLINES);
30 30  
31 31 $td->report($n_tests);
... ...
libtests/qtest/objects/test2.out 0 → 100644
  1 +WARNING: empty PDF: limits error(max-stream-filters): too many filters for stream; treating stream as not filterable
  2 +test 2 done
... ...
manual/cli.rst
... ... @@ -3822,7 +3822,6 @@ Parser Limits
3822 3822 Set the maximum nesting level while parsing objects. The maximum nesting level is not
3823 3823 disabled by :qpdf:ref:`--no-default-limits`. Defaults to 499.
3824 3824  
3825   -
3826 3825 .. qpdf:option:: --parser-max-errors=n
3827 3826  
3828 3827 .. help: set the maximum number of errors while parsing
... ... @@ -3848,7 +3847,6 @@ parsing. The limit applies when the PDF document&#39;s xref table is undamaged
3848 3847 and the object itself can be parsed without errors. The default limit
3849 3848 is 4,294,967,295. See also :qpdf:ref:`--parser-max-container-size-damaged`.
3850 3849  
3851   -
3852 3850 .. qpdf:option:: --parser-max-container-size-damaged=n
3853 3851  
3854 3852 .. help: set the maximum container size while parsing damaged files
... ... @@ -3866,6 +3864,25 @@ or the object itself is damaged. The limit also applies when parsing
3866 3864 xref streams. The default limit is 5,000.
3867 3865 See also :qpdf:ref:`--parser-max-container-size`.
3868 3866  
  3867 +
  3868 +Stream and Filter Limits
  3869 +.........................
  3870 +
  3871 +.. qpdf:option:: --max-stream-filters=n
  3872 +
  3873 + .. help: set the maximum number of filters allowed when filtering streams
  3874 +
  3875 + An excessive number of stream filters is usually a sign that a file
  3876 + is damaged or specially constructed. If the maximum is exceeded for
  3877 + a stream the stream is treated as unfilterable.
  3878 + The default limit is 25.
  3879 +
  3880 +Set the maximum number of filters allowed when filtering streams. An excessive
  3881 +number of stream filters is usually a sign that a file is damaged or specially
  3882 +constructed. If the maximum is exceeded for a stream the stream is treated as
  3883 +unfilterable. The default limit is 25.
  3884 +
  3885 +
3869 3886 .. _test-options:
3870 3887  
3871 3888 Options for Testing or Debugging
... ...
manual/qpdf.1
... ... @@ -1236,6 +1236,14 @@ parsing. The limit applies when the PDF document&#39;s xref table is damaged
1236 1236 or the object itself is damaged. The limit also applies when parsing
1237 1237 xref streams. The default limit is 5,000.
1238 1238 See also --parser-max-container-size.
  1239 +.TP
  1240 +.B --max-stream-filters \-\- set the maximum number of filters allowed when filtering streams
  1241 +--max-stream-filters=n
  1242 +
  1243 +An excessive number of stream filters is usually a sign that a file
  1244 +is damaged or specially constructed. If the maximum is exceeded for
  1245 +a stream the stream is treated as unfilterable.
  1246 +The default limit is 25.
1239 1247 .SH TESTING (options for testing or debugging)
1240 1248 The options below are useful when writing automated test code that
1241 1249 includes files created by qpdf or when testing qpdf itself.
... ...
manual/release-notes.rst
... ... @@ -106,6 +106,12 @@ more detail.
106 106  
107 107 - Other changes
108 108  
  109 + - By default, streams with more than 25 filters are now treated as unfilterable.
  110 + A large number of filters typically occur in damaged or specially constructed
  111 + files and can cause excessive use of resources and/or stack overflows. The
  112 + limit can be changed if necessary with the new :qpdf:ref:`--max-stream-filters`
  113 + CLI option or the new ``qpdf::global::max_stream_filters`` function.
  114 +
109 115 - When running in a FIPS environment using the GnuTLS crypto provider,
110 116 calls to GnuTLS now use 'LAX' mode as the use of weak algorithms is
111 117 required to decrypt existing files and is specified by the PDF standards
... ...