solana: закрыть griefing создания PDA и заморозку выплат, добавить аудит №2

shine_payments + shine_users:
- create_pda_account переведён на «создание поверх предзаполненного»
  (allocate+assign+добор ренты), чтобы подсев лампортов на детерминированный
  адрес PDA (тикет/логин) не блокировал создание — закрыт LOW из аудита №1;
  в shine_payments is_uninitialized_account перестала зависеть от баланса.

shine_payments (HIGH из аудита №2):
- запрещён recipient == inflow_vault в buy_ticket*, manager_add_ticket и
  change_ticket_recipient; добавлена защита по умолчанию в transfer_from_vault
  (require vault.key != recipient.key). Это убирает алиасинг аккаунта в
  step_payout, который навсегда замораживал очередь выплат и средства вольта.

Документация и учёт:
- doc/programs/shine_payments.md §3.4, §10.1; doc/programs/shine_users.md §3.3;
- Dev_Docs/audit: добавлен аудит №2, обе закрытые находки помечены ИСПРАВЛЕНО;
- Dev_Docs/Pending_Features: две записи на ручную e2e-проверку на devnet;
- VERSION.properties: client 1.2.161, server 1.2.150.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
AidarKC
2026-06-11 04:10:31 +04:00
co-authored by Claude Fable 5
parent 01d9553db4
commit cf6a2830c8
9 changed files with 389 additions and 16 deletions
@@ -73,7 +73,19 @@ read_sol_usd_price / parse_pyth_price_update_v2 (строки 10381075):
Проверка возраста цены (ORACLE_MAX_AGE_SECS = 120) есть и сделана корректно. Рекомендация: проверять владельца аккаунта, сверять feed_id с константой и валидировать verification_level == Full (или парсить через официальный pyth_solana_receiver_sdk, который уже завендорен в .vendor/).
---
🟡 LOW — DoS через предсказуемые адреса тикетов
🟡 LOW — DoS через предсказуемые адреса тикетов — ✅ ИСПРАВЛЕНО (11.06.2026)
Закрыто: `create_pda_account` в `shine_payments` и `shine_users` переведён на паттерн
«создание поверх предзаполненного» (allocate + assign + добор ренты вместо строгого
`system_instruction::create_account`). «Подсев» лампортов на заранее известный адрес
тикета или пользовательской записи больше не блокирует создание PDA. Проверка
`is_uninitialized_account` в payments перестала зависеть от нулевого баланса. Тот же фикс
закрывает аналогичный сквоттинг логинов в `shine_users` (адрес выводится из логина).
Подробности — в `doc/programs/shine_payments.md` §3.4 и `doc/programs/shine_users.md` §3.3.
Историческое описание находки ниже.
is_uninitialized_account (строка 1195) считает аккаунт неинициализированным только если lamports() == 0. Адреса тикетов детерминированы (queue_seed + index), а индекс последователен и предсказуем. Любой может заранее перевести немного лампортов на адрес следующего тикета — тогда create_pda_account упадёт (PdaAlreadyExists / ошибка create_account), заблокировав покупку/добавление тикета. Это griefing-DoS, не кража. Митигировать можно паттерном «create поверх предзаполненного» (allocate + assign + добор ренты) вместо system_instruction::create_account.