This repository was archived by the owner on May 27, 2026. It is now read-only.
Commit 488fe69
committed
python: escape rule values in generated validator source (string code injection)
The Python validator generates Python source from Jinja2 templates and then
exec()'s it (`python/protoc_gen_validate/validator.py:68` and `:217`). Six
template sites in `string_template` and one in `const_template` interpolate
string-typed `validate.rules` option values between literal Python quote
characters, e.g.
if {{ name }} != "{{ o.string['const'] }}":
if not "{{ s['contains'] }}" in {{ name }}:
if re.search(r'{{ s['pattern'] }}', {{ name }}) is None:
A `.proto` file with a crafted rule string can close the surrounding Python
string literal and append arbitrary expressions or statements, which run on
the first call to `validate(msg)` for that descriptor (the generated
function is cached via `@lru_cache`).
This is the Python analogue of the Java code-injection class tracked in
issue/PR #1385.
Fix
---
Add a small `_pyrepr` helper that returns `repr(value)`, the canonical
Python-source literal representation, and pass it as `repr=` into every
affected `Template(...).render(...)` call. Rewrite each unsafe
`"{{ ... }}"` interpolation as `{{ repr(...) }}` so the value becomes a
single, properly-escaped Python literal. Error messages that previously
interpolated the same value inside their format string are restructured to
do runtime string concatenation with a separately-escaped literal, e.g.
raise ValidationFailed("p.field does not contain " + {{ repr(s['contains']) }})
This preserves the existing user-visible error text on benign input,
keeps regex `{{ s['pattern'] }}` semantics (Python's `repr` produces a
non-raw literal whose backslashes evaluate back to the original regex),
and refuses injection on crafted input.
Reproduction (before patch)
---------------------------
string evil = 1 [(validate.rules).string.contains =
"x\" + __import__('os').system('touch /tmp/pgv-pwned') + \"y"];
Running validate(msg) on a message of this type touches
`/tmp/pgv-pwned`. After this patch the same payload is embedded as a
Python string literal `'x" + __import__(\'os\').system(...) + "y'` and
no command runs.
Sinks closed
------------
* `const_template` string/bool/bytes `const`
* `in_template` `in` / `not_in` (error message)
* `string_template` `pattern`, `prefix`, `suffix`, `contains`, `not_contains`1 parent 92b9a7d commit 488fe69
1 file changed
Lines changed: 38 additions & 24 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
24 | 38 | | |
25 | 39 | | |
26 | 40 | | |
| |||
300 | 314 | | |
301 | 315 | | |
302 | 316 | | |
303 | | - | |
304 | | - | |
| 317 | + | |
| 318 | + | |
305 | 319 | | |
306 | 320 | | |
307 | | - | |
| 321 | + | |
308 | 322 | | |
309 | 323 | | |
310 | | - | |
311 | | - | |
| 324 | + | |
| 325 | + | |
312 | 326 | | |
313 | | - | |
314 | | - | |
| 327 | + | |
| 328 | + | |
315 | 329 | | |
316 | 330 | | |
317 | 331 | | |
318 | | - | |
| 332 | + | |
319 | 333 | | |
320 | 334 | | |
321 | 335 | | |
322 | 336 | | |
323 | 337 | | |
324 | | - | |
325 | | - | |
| 338 | + | |
| 339 | + | |
326 | 340 | | |
327 | 341 | | |
328 | | - | |
329 | | - | |
| 342 | + | |
| 343 | + | |
330 | 344 | | |
331 | 345 | | |
332 | | - | |
| 346 | + | |
333 | 347 | | |
334 | 348 | | |
335 | 349 | | |
| |||
376 | 390 | | |
377 | 391 | | |
378 | 392 | | |
379 | | - | |
380 | | - | |
| 393 | + | |
| 394 | + | |
381 | 395 | | |
382 | 396 | | |
383 | | - | |
384 | | - | |
| 397 | + | |
| 398 | + | |
385 | 399 | | |
386 | 400 | | |
387 | | - | |
388 | | - | |
| 401 | + | |
| 402 | + | |
389 | 403 | | |
390 | 404 | | |
391 | | - | |
392 | | - | |
| 405 | + | |
| 406 | + | |
393 | 407 | | |
394 | 408 | | |
395 | | - | |
396 | | - | |
| 409 | + | |
| 410 | + | |
397 | 411 | | |
398 | 412 | | |
399 | 413 | | |
| |||
446 | 460 | | |
447 | 461 | | |
448 | 462 | | |
449 | | - | |
| 463 | + | |
450 | 464 | | |
451 | 465 | | |
452 | 466 | | |
| |||
0 commit comments