fix(security): защита CGI агента, права конфига и захват лока
- агентский CGI: /diagnose мог удалять nft-таблицы, переписывать resolv.conf и перезапускать sing-box с любого устройства LAN без токена; bearer-токен теперь передаётся через окружение вместо argv (виден в ps), тело запроса ограничено 64KB до чтения, wildcard Access-Control-Allow-Origin убран - произвольные сайты больше не могут читать состояние роутера кросс-доменно (включая DNS rebinding) - agent_api: сравнение токена за константное время - uci/migration: /etc/config/tachyon хранит Telegram bot token и ссылки на подписки; UCI не сохраняет 0600 при коммитах, поэтому режим ужесточается после каждого коммита этого конфига и после миграций - build.yml: убран NODE_TLS_REJECT_UNAUTHORIZED=0 из шагов публикации релиза - проверка сертификатов не должна отключаться на раннере, заливающем релизы - initd/state: устранено окно TOCTOU при захвате устаревшего reload-лока: два ожидающих могли оба выполнить rm+rmdir+mkdir и каждый считал лок своим; захват теперь атомарным переименованием, которое выигрывает ровно один претендент
This commit is contained in:
@@ -270,20 +270,36 @@ function acquire_runtime_dir_lock(lock_dir, owner_pid) {
|
||||
|
||||
let lock_stat = fs.stat(lock_dir + "/pid") || fs.stat(lock_dir);
|
||||
let lock_age = (lock_stat && lock_stat.mtime) ? (int(clock()[0]) - lock_stat.mtime) : 9999;
|
||||
let recorded_pid = first_line_value(lock_dir + "/pid");
|
||||
|
||||
if (pid_alive(first_line_value(lock_dir + "/pid")) && lock_age < 300)
|
||||
if (pid_alive(recorded_pid) && lock_age < 300)
|
||||
return false;
|
||||
|
||||
command_success_from_args([ "rm", "-f", lock_dir + "/pid" ]);
|
||||
if (!command_success_from_args([ "rmdir", lock_dir ]))
|
||||
return false;
|
||||
if (!command_success_from_args([ "mkdir", lock_dir ]))
|
||||
// pid file missing entirely: either another waiter is mid-takeover or a
|
||||
// previous owner crashed before writing it. Only steal once the
|
||||
// directory is old enough that a live holder cannot still be starting.
|
||||
if (recorded_pid == "" && lock_age < 300)
|
||||
return false;
|
||||
|
||||
if (lock_dir_write_owner(lock_dir, owner_pid))
|
||||
return true;
|
||||
// Takeover must be atomic: rm+rmdir+mkdir lets two concurrent waiters
|
||||
// each believe they own the lock. Renaming the directory away succeeds
|
||||
// for exactly one contender; the losers re-race on mkdir and either win
|
||||
// a fresh lock or observe the winner's pid file.
|
||||
let stale_dir = lock_dir + ".stale." + owner_pid + "." + int(clock()[0]);
|
||||
if (!command_success_from_args([ "mv", lock_dir, stale_dir ]))
|
||||
return false;
|
||||
|
||||
release_runtime_dir_lock(lock_dir);
|
||||
command_success_from_args([ "rm", "-rf", stale_dir ]);
|
||||
|
||||
if (command_success_from_args([ "mkdir", lock_dir ])) {
|
||||
if (lock_dir_write_owner(lock_dir, owner_pid))
|
||||
return true;
|
||||
release_runtime_dir_lock(lock_dir);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Another contender re-created the lock between our rename and mkdir:
|
||||
// they own it now.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user