Commit e9763e9
Trojan Source: имя FTS уезжало сырым в комментарии — и во весь ftsc
Двунаправленные управляющие символы Unicode (U+061C, U+200E, U+200F,
U+202A…U+202E, U+2066…U+2069) места не занимают, а раскладку текста меняют,
поэтому файл читается не так, как исполняется: CVE-2021-42574. Прошлый шаг
закрыл их в строковых литералах восьми целей flang. Оставались две дыры, и
вторая шире первой.
ДЫРА ПЕРВАЯ — комментарии flang. Имя FTS уезжало в напечатанный код сырым у всех
восьми целей одинаково: шапка файла («Модуль flang: «…»»), документация функции
(«Функция flang «…»»), подпись параметра, javadoc, `///`, @moduledoc, docstring,
комментарий Cargo.toml и .csproj. Комментарий не исполняется — ровно поэтому он
и опасен: его читают первым, а проверить исполнением не могут.
ДЫРА ВТОРАЯ — весь ftsc. Правила не было НИ В ОДНОМ из восьми бэкендов, и имя
правила с U+202E доезжало сырым сразу двумя путями: в комментарий («правило
«…»», «свойство «…»») и в строковый литерал (System.out.println прогонщика
примеров, `test "…"` ExUnit, имя нарушенного свойства в тексте исключения).
Чего это стоило (замерено на этой машине, программа с именем «Спис<U+202E>ание
<U+202C>» и фикстура discount с такими же именами):
• rustc 1.96 — ОШИБКА сборки: `text_direction_codepoint_in_comment` и
`…_in_literal` deny-by-default. Напечатанный крейт не собирался вовсе — ни у
flang, ни у ftsc;
• elixirc 1.20.2 — ОТКАЗ разбора: «invalid bidirectional formatting character
in comment/string». Тоже полный отказ, а не придирка;
• gcc 13.3 под -Werror — ошибка на НЕПАРНОМ управляющем в комментарии
(-Wbidi-chars=unpaired включён по умолчанию), но парную пару RLO…PDF он
пропускает молча: его молчание ничего не доказывает;
• javac 25 (-Xlint:all -Werror), CPython 3.12, node 24 — молчат. Это не
смягчающее обстоятельство, а отягчающее: ради этого молчания атаку и
придумали.
ПОЧИНКА. Набор кодовых точек один на все шестнадцать бэкендов обоих компиляторов
и вынесен в tools/ftsc/src/bidi.mjs — туда же, откуда бэкенды flang уже берут
naming.mjs. Восемь копий набора, разъехавшихся по бэкендам flang, заменены
импортом. Форма записи общей быть не может, и их ровно три: `\uXXXX` (Java, C#,
Go, Python, JS/TS), `\u{X…}` (Rust, Elixir), байты UTF-8 восьмеричными (C — в C99
узкая строка байт-точна только так). Выбирает форму бэкенд: он один знает, что
понимает его компилятор.
Место — последний шаг emit(): escapeBidiInFiles снимает сырые управляющие со
всего вывода. Почему фильтром, а не заботой в каждом месте склейки: мест, где
имя FTS попадает в файл, у одного бэкенда десятки, и правило, записанное в
каждом из них, держится ровно до следующего нового места. К этому шагу строковые
литералы уже экранированы своей функцией литерала (она знает про кавычки и
слэши), поэтому сырым остаётся только текст, который язык не разбирает.
Экранирование не удваивается: обратный слэш печатается раньше. Пути файлов
фильтр не трогает намеренно — они собраны транслитерацией и состоят из ASCII, а
экранирование в имени файла разошлось бы с тем, чего ждут компилятор и Makefile;
что сырых там нет, проверяют тесты.
ЗНАЧЕНИЕ НЕ ИЗМЕНИЛОСЬ — проверено сборкой, а не рассуждением. Программа со
всеми двенадцатью управляющими в имени модуля, имени функции и литерале
напечатана во все цели; собранные C (gcc 13.3), Java (javac 25, -Xlint:all
-Werror), Elixir (elixirc 1.20.2 / OTP 29, --warnings-as-errors), Rust (rustc
1.96) и Python 3.12 отвечают на вызов по имени с сырыми управляющими и
возвращают строку, совпадающую с интерпретатором байт в байт: 45 байт, 22
кодовые точки у всех пяти. На стороне ftsc собранный C печатает имя примера теми
же байтами, что стоят в IR (сверено od -c), прогонщик примеров Java и ExUnit
Elixir — тоже (пример намеренно завален, чтобы имя было напечатано).
ТЕСТЫ — общие, перебором ПО РЕЕСТРУ ЦЕЛЕЙ, а не по списку в тесте:
• flang/test/emit-bidi.test.mjs расширен: программа несёт управляющие трижды —
в имени модуля, в имени функции и в литерале, — а проверяется ВЕСЬ
напечатанный файл и имя файла, а не найденный в файле литерал;
• tools/ftsc/test/emit-bidi.test.mjs заведён с нуля: реестр там тоже каталогом
(src/targets.mjs), поэтому приём тот же. Источник истины для имён — движок
FTS (evaluateUtility), тот самый, которым пользуется сам ftsc;
• инструмент проверки общий — tools/ftsc/test/bidi-guard.mjs: правило одно на
оба компилятора, значит и способ проверить обязан быть один. Набор кодовых
точек там СВОЙ, не импортированный из src/bidi.mjs: иначе выпавшая из набора
точка исчезла бы разом и из бэкендов, и из проверки.
Тест ловит регресс — проверено подкладыванием девятой цели каждому компилятору:
цель, печатающая сырыми, цель, теряющая управляющие при печати, цель, сдвигающая
кодовую точку на единицу, и цель, уносящая сырой символ в ИМЯ ФАЙЛА, — краснеют
все четыре. Снятие фильтра у flang/go и ftsc/java тоже краснеет; после возврата
оба теста зелёные.
Попутно: из комментария flang/src/emit/js.mjs убран сырой U+202A — единственный
на весь репозиторий. Файл, стерегущий от Trojan Source, сам его и содержал.
Контракт бэкенда записан там, где его читают: tools/ftsc/SPEC.md, п. 8 (новый), и
таблица «Семантика, которая обязана совпадать у всех слоёв» в flang/SPEC.md.
НЕ ПРОВЕРЕНО СБОРКОЙ: Go и C# — тулчейнов go и .NET на машине нет. Для них
проверена только печать: `\uXXXX` в комментарии и в литерале, значение то же по
разбору. Тесты сборки C# в наборе есть и честно пропускаются.
НЕ ТРОНУТО, найдено попутно: tools/ftsvm/src/jit.mjs печатает имя правила в
комментарий генерируемого JS (commentText) и в литерал (JSON.stringify) без
экранирования двунаправленных. Это третья поверхность печати кода в репозитории
— отдельная работа.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P6ehGNnEcrkCK1V5iMHNYi1 parent 90ecb27 commit e9763e9
22 files changed
Lines changed: 768 additions & 284 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
265 | 265 | | |
266 | 266 | | |
267 | 267 | | |
| 268 | + | |
268 | 269 | | |
269 | 270 | | |
270 | 271 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| 72 | + | |
72 | 73 | | |
73 | 74 | | |
74 | 75 | | |
| |||
325 | 326 | | |
326 | 327 | | |
327 | 328 | | |
328 | | - | |
329 | | - | |
330 | | - | |
331 | | - | |
332 | | - | |
333 | | - | |
334 | | - | |
335 | | - | |
336 | | - | |
337 | | - | |
338 | | - | |
339 | | - | |
340 | | - | |
341 | | - | |
342 | | - | |
343 | | - | |
344 | | - | |
345 | | - | |
346 | 329 | | |
347 | 330 | | |
348 | 331 | | |
| |||
533 | 516 | | |
534 | 517 | | |
535 | 518 | | |
536 | | - | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
537 | 528 | | |
538 | 529 | | |
539 | 530 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
| 86 | + | |
86 | 87 | | |
87 | 88 | | |
88 | 89 | | |
| |||
388 | 389 | | |
389 | 390 | | |
390 | 391 | | |
391 | | - | |
392 | | - | |
393 | | - | |
394 | | - | |
395 | | - | |
396 | | - | |
397 | | - | |
398 | | - | |
399 | | - | |
400 | | - | |
401 | | - | |
402 | | - | |
403 | | - | |
404 | | - | |
405 | | - | |
406 | | - | |
407 | | - | |
408 | 392 | | |
409 | 393 | | |
410 | 394 | | |
| |||
607 | 591 | | |
608 | 592 | | |
609 | 593 | | |
610 | | - | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
611 | 601 | | |
612 | 602 | | |
613 | 603 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
99 | 99 | | |
100 | 100 | | |
101 | 101 | | |
| 102 | + | |
102 | 103 | | |
103 | 104 | | |
104 | 105 | | |
| |||
385 | 386 | | |
386 | 387 | | |
387 | 388 | | |
388 | | - | |
389 | | - | |
390 | | - | |
391 | | - | |
392 | | - | |
393 | | - | |
394 | | - | |
395 | | - | |
396 | | - | |
397 | | - | |
398 | | - | |
399 | | - | |
400 | | - | |
401 | | - | |
402 | | - | |
403 | | - | |
404 | 389 | | |
405 | 390 | | |
406 | 391 | | |
| |||
631 | 616 | | |
632 | 617 | | |
633 | 618 | | |
634 | | - | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
635 | 627 | | |
636 | 628 | | |
637 | 629 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
| 97 | + | |
97 | 98 | | |
98 | 99 | | |
99 | 100 | | |
| |||
372 | 373 | | |
373 | 374 | | |
374 | 375 | | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
375 | 382 | | |
376 | | - | |
377 | | - | |
378 | | - | |
379 | | - | |
380 | | - | |
381 | | - | |
382 | | - | |
383 | | - | |
384 | | - | |
385 | | - | |
386 | | - | |
387 | | - | |
388 | | - | |
389 | | - | |
390 | 383 | | |
391 | 384 | | |
392 | 385 | | |
| |||
546 | 539 | | |
547 | 540 | | |
548 | 541 | | |
549 | | - | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
550 | 549 | | |
551 | 550 | | |
552 | 551 | | |
| |||
0 commit comments