fix: advance handshake stage only after the core accepts a packet - #30
fix: advance handshake stage only after the core accepts a packet#30Woralem wants to merge 2 commits into
Conversation
как его увеличивать пользователю или разработчику написавший модуль? может не стоит добавлять таймаут? |
Убирать таймаут не стоит: бесконечный Предлагаю условно так, что:
Про «(CLI, WebUI и прочие) обработать исключение» - согласен полностью. Сорянчик еще что пару дней отсутствовал, мне будет довольно сложно заниматься параллельно этим проектом пусть мне он и нравиться, в основном из-за того что сейчас я нахожусь в другом городе на отдыхе с родными, и продолжать активную разработку здесь смогу только ближе к 2-3 сентября |
Да, давай. Еще надо в документацию про это написать |
|
Сделал,
Документация - подраздел «9. Необязательное поле handshake_timeout» в конце Проверял на реальном |
Fix: handshake deadlock on malformed service packets
Короче
Один некорректный служебный пакет, отправленный до честного собеседника, необратимо
вешал рукопожатие навсегда: поток инициализации оставался на
Event.wait()безтаймаута. Проще говоря дедлок
Причина
На уровне приложения этап рукопожатия
HANDSHAKE_STAGEпереключался до того,как ядро проверит содержимое пакета. Методы
receive_node_id/receive_sign/receive_public_keyничего не возвращали, поэтому уровень приложения не моготличить принятые данные от отброшенных
WAIT_SIGNдоreceive_node_id. Если node id не проходилcheck_node_id, корректный node id, пришедший следующим, отбрасывался ужепроверкой этапа
SIGN_RECEIVEDдоreceive_sign. Разбор мусорной точкикривой падал исключением (его глотал
Base._pump), этап оставался сдвинутым,и корректная подпись уже не принималась
READYСоответствующий
Eventпри этом не выставлялся, а ожидание было бессрочным, поэтомуinit()вставал навсегдаХде
src/levels/application.py- веткиMY_NODE_ID,MY_SIGN,MY_PUBLIC_KEYвhandle_packet: присвоениеHANDSHAKE_STAGEстояло перед вызовомreceive_*src/crypto_layer.py-COMPANION_NODE_ID_RECEIVED.wait(),COMPANION_SIGN_RECEIVED.wait(),COMPANION_PUBLIC_KEY_RECEIVED.wait()без таймаутаsrc/crypto_layer.py-receive_node_id/receive_sign/receive_public_keyне сообщали результат наверх; разбор точки кривой не был изолирован
Что сделано
src/levels/application.py- этап только после подтверждения приёма126,141,158-receive_node_id/receive_sign/receive_public_keyвызываются до смены этапа, и этап меняется только при их успехе
(
if not ...: return)120-124- полезная нагрузкаMY_NODE_IDдекодируется безопасно: невалидный UTF-8логируется и отбрасывается без смены этапа
src/crypto_layer.py- контролируемый разбор и конечное ожидание34-EC_COMPRESSED_POINT_LENGTH = 33: ожидаемая длина точки SECP256R1 в сжатомформате X9.62. Обе стороны отправляют ключи только в этом виде
104,109- таймауты шагов рукопожатия берутся изconfig.py:HANDSHAKE_TIMEOUTи отдельныйHANDSHAKE_USER_CHECK_TIMEOUT133-149- рукопожатие внутриinit()обёрнуто вtry/except: при сбое ошибкауходит в UI со статусом
error, вызываетсяabort_init(), исключениепробрасывается вызывающему.
on_ready()в этом случае не вызывается164- новыйabort_init(): пароль стирается из RAM, потоки уровней и модуляостанавливаются (
Base.stop_event,BaseModule.stop_event).DISCONNECTнеотправляем - рукопожатие не состоялось и подписывать пакет нечем
311,334,430-434- три бесконечных ожидания заменены наwait_handshake_step.Шаг ECDH-ключа ждёт по
HANDSHAKE_USER_CHECK_TIMEOUT, остальные - поHANDSHAKE_TIMEOUT491-wait_handshake_step(event, step_name, timeout=None): ждёт шаг не дольшелимита, при истечении логирует и бросает
TimeoutErrorс именем шага501-receive_node_id(...) -> bool: при провалеcheck_node_idвозвращаетFalse516-receive_sign(...) -> bool: сначала проверка длины payload, затем разбор точкив
try/except (ValueError, TypeError). При отказеEventне выставляется иCOMPANION_SIGNне перезаписывается539-receive_public_key(...) -> bool: то же самое для ECDH-ключаsrc/config.py- лимиты рядом с остальными настройками24-HANDSHAKE_TIMEOUT = 60: обычный шаг рукопожатия. На медленном модуле(редкий опрос мессенджера, повторы транспорта) значение стоит увеличить
29-HANDSHAKE_USER_CHECK_TIMEOUT = 900: шаг, который ждёт действия человека.Собеседник отправляет ECDH-ключ только после того, как вручную сверит отпечаток
подписи по доверенному каналу, поэтому единый минутный лимит рвал бы честное
рукопожатие
Изменение контракта
init()Раньше
init()при неудачном рукопожатии не возвращался никогда. Теперь он можетбросить исключение (
TimeoutErrorпо таймауту шага,TypeErrorпри отказе ототпечатка). К этому моменту уровни и модуль уже остановлены, пароль стёрт, объект
повторному использованию не подлежит - нужен новый
CryptoLayer. Вызывающему коду(CLI, WebUI, и прочие) следует обработать исключение и показать ошибку
вместо бесконечного «Loading...»
Всех обнял, поцеловал <3