SHA256
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:
co-authored by
Claude Fable 5
parent
01d9553db4
commit
cf6a2830c8
@@ -1007,11 +1007,35 @@ fn transfer_lamports<'a>(payer: &AccountInfo<'a>, recipient: &AccountInfo<'a>, s
|
||||
invoke(&ix, &[payer.clone(), recipient.clone(), system_program_ai.clone()])
|
||||
}
|
||||
|
||||
// Создание PDA, устойчивое к «минированию» детерминированного адреса.
|
||||
// Адрес будущей записи логина выводится из самого логина, поэтому злоумышленник
|
||||
// может заранее вычислить адрес и перевести на него немного лампортов обычным
|
||||
// system-переводом. Тогда обычный system_instruction::create_account упал бы
|
||||
// («account already in use») и заблокировал бы регистрацию этого логина навсегда.
|
||||
// Чтобы это исключить, при уже существующих на адресе лампортах создаём аккаунт
|
||||
// «поверх предзаполненного»: доводим ренту переводом, затем allocate + assign
|
||||
// под подписью PDA. Подсев чужих лампортов больше ничего не ломает.
|
||||
fn create_pda_account<'a>(payer: &AccountInfo<'a>, pda: &AccountInfo<'a>, system_program_ai: &AccountInfo<'a>, owner: &Pubkey, seeds: &[&[u8]], space: usize) -> ProgramResult {
|
||||
let rent = Rent::get()?;
|
||||
let lamports = rent.minimum_balance(space);
|
||||
let ix = system_instruction::create_account(payer.key, pda.key, lamports, space as u64, owner);
|
||||
invoke_signed(&ix, &[payer.clone(), pda.clone(), system_program_ai.clone()], &[seeds])
|
||||
let required_lamports = rent.minimum_balance(space);
|
||||
let current_lamports = pda.lamports();
|
||||
|
||||
if current_lamports == 0 {
|
||||
// Быстрый путь: адрес пуст — обычное создание аккаунта одной инструкцией.
|
||||
let ix = system_instruction::create_account(payer.key, pda.key, required_lamports, space as u64, owner);
|
||||
return invoke_signed(&ix, &[payer.clone(), pda.clone(), system_program_ai.clone()], &[seeds]);
|
||||
}
|
||||
|
||||
// На адресе уже лежат лампорты (вероятно, «подсев» атакующим). Доводим баланс
|
||||
// до рент-экземпта, выделяем место и назначаем владельцем нашу программу.
|
||||
let top_up = required_lamports.saturating_sub(current_lamports);
|
||||
transfer_lamports(payer, pda, system_program_ai, top_up)?;
|
||||
|
||||
let allocate_ix = system_instruction::allocate(pda.key, space as u64);
|
||||
invoke_signed(&allocate_ix, &[pda.clone(), system_program_ai.clone()], &[seeds])?;
|
||||
|
||||
let assign_ix = system_instruction::assign(pda.key, owner);
|
||||
invoke_signed(&assign_ix, &[pda.clone(), system_program_ai.clone()], &[seeds])
|
||||
}
|
||||
|
||||
fn ensure_pda_size_and_rent<'a>(pda: &AccountInfo<'a>, payer: &AccountInfo<'a>, system_program_ai: &AccountInfo<'a>, required_len: usize) -> ProgramResult {
|
||||
|
||||
Reference in New Issue
Block a user