fix(spawn): закрывать дескрипторы фоновых процессов вместо фиктивного 1000<&-
ucode держит по одному открытому дескриптору на каждый require()-модуль всё время жизни интерпретатора и не даёт ни закрыть их, ни пометить close-on-exec, поэтому каждый фоновый процесс наследует весь набор. На роутере watchdog несёт около 90 таких дескрипторов и передаёт их детям: logread, sh и nfqws2 найдены держащими дескрипторы на собственных .uc-файлах Tachyon, часть уже удалена обновлением пакета — старый инод прибит к диску процессом, которому он не нужен. Против этого код писал литерал 1000<&- в 22 местах 13 модулей. Он не закрывал ничего: дескриптор 1000 никогда не открывается, а все настоящие лежат в 6..89. Хуже того, этого литерала требовал tests/service_start_trap.sh — именно поэтому он и разошёлся по кодовой базе. Проверка пинила видимость защиты и продолжала бы проходить вечно при живой утечке. Замена оказалась не такой прямой, как выглядит. Счёт от 3 до потолка не годится: в dash закрытие дескриптора 10 и выше фатально даже для номера, который никогда не открывался — dash перемещает туда собственную служебную бухгалтерию, и перенаправление убивает шелл целиком, а 2>/dev/null прячет сообщение, но не смерть. Перечисление /proc/self/fd трогает только реально открытое, но сами высокие дескрипторы никуда не деваются: набор модулей любого размера уводит их за 10. Поэтому пролог сначала спрашивает шелл в подоболочке, которая поглощает смерть, и опускает собственный потолок, если ответ отрицательный. busybox ash, на котором работает OpenWrt, отвечает утвердительно и закрывает весь набор. Три хелпера в core/common.uc задают форму фонового запуска — раньше каждое место собирало перенаправления вручную, и они разошлись. Форма с пидом ставит exec внутрь подоболочки, чтобы $! называл сам демон, а не обёртку, которая тут же завершается; ведущие присваивания VAR=x при этом выносятся перед exec, иначе шелл ищет программу с именем VAR=x. Для конвейеров, которые exec заменить не может, есть отдельный хелпер. initd.uc и state.uc лежат на пути раннего старта и несут собственные копии, как уже несут shell_quote и command_from_args. Попутно вскрылись два настоящих дефекта. В лестнице эскалации watchdog строка читалась как restart & rm -f lock &, то есть лок снимался параллельно с рестартом, который он охраняет, и вторая эскалация могла начаться посреди первой. В пакетном прогреве Zero-RTT знак & стоял внутри строки, поэтому перенаправления после него цеплялись к пустой команде в родительском шелле, а не к фоновому циклу. Ещё четыре места запускали фон вообще мимо механизма; одно из них аудит пропустил, его нашёл новый тест. tests/background_spawn_fds.sh считает дескрипторы у ребёнка при обоих способах запуска: структурная проверка такое показать не может, строку надо выполнить. На роутере старый способ даёт ребёнку 7 дескрипторов, новый — 3.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env ucode
|
||||
|
||||
let fs = require("fs");
|
||||
let common = require("core.common");
|
||||
let helpers = require("core.helpers");
|
||||
let uci_core = require("core.uci");
|
||||
let connections = require("config.connections");
|
||||
@@ -1259,9 +1260,8 @@ function launch_subscription_worker(args) {
|
||||
for (let arg in args)
|
||||
push(command_args, arg);
|
||||
|
||||
let command = command_env(subscription_worker_env()) + " " +
|
||||
command_from_args(command_args) +
|
||||
" >/dev/null 2>&1 1000>&- & echo $!";
|
||||
let command = common.background_command_with_pid(
|
||||
command_env(subscription_worker_env()) + " " + command_from_args(command_args));
|
||||
return trim(command_output("sh -c " + shell_quote(command)));
|
||||
}
|
||||
|
||||
@@ -1761,9 +1761,8 @@ function launch_component_worker(args) {
|
||||
for (let arg in args)
|
||||
push(command_args, arg);
|
||||
|
||||
let command = command_env(component_worker_env()) + " " +
|
||||
command_from_args(command_args) +
|
||||
" >/dev/null 2>&1 1000>&- & echo $!";
|
||||
let command = common.background_command_with_pid(
|
||||
command_env(component_worker_env()) + " " + command_from_args(command_args));
|
||||
return trim(command_output("sh -c " + shell_quote(command)));
|
||||
}
|
||||
|
||||
@@ -2345,7 +2344,7 @@ function import_builtin_subnets_from_rule(section, settings) {
|
||||
write_file(cached_file, join("\n", combined_lines) + "\n");
|
||||
ensure_dir("/etc/tachyon/rulesets");
|
||||
write_file(persistent_file, join("\n", combined_lines) + "\n");
|
||||
system("/etc/init.d/tachyon reload </dev/null >/dev/null 2>&1 &");
|
||||
system(common.background_command("/etc/init.d/tachyon reload"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user