Puntos Clave de Seguridad de Programas
La seguridad de los programas de Solana es seguridad de cuentas. Los llamadores eligen qué cuentas y programas entran en una instrucción; Sealevel aplica bloqueos, reglas de escritura de propiedad y presupuestos de cómputo, no el saldo de tu bóveda, la lista de administradores o la vinculación de la menta.
Esta página es el mapa conceptual para Seguridad y Auditoría de Programas: superficie de ataque de Sealevel, validación de cuentas, comprobaciones de firmante y propietario, ataques de PDA y semillas, reinicialización, aritmética, riesgo de CPI y reentrada, y un flujo de trabajo de auditoría práctico en Agave 4.1.1.
Resumen
- Cada instrucción es una interfaz adversarial: demuestra quién firmó, quién posee cada cuenta, qué PDA y tipo tocas, luego muta el estado con matemáticas comprobadas y orden de CPI seguro.
- Por Qué Importa: Los exploits rara vez necesitan una ruptura criptográfica novedosa. Sustituyen una bóveda incorrecta, omiten un firmante, reinicializan un administrador, envuelven un saldo o hacen una CPI a un programa malicioso que devuelve "éxito".
- Conceptos Clave: validación de cuentas, comprobaciones de firmante/propietario, semillas y bump de PDA, guardas de reinicialización, aritmética comprobada, listas blancas de CPI, interacciones de efectos de comprobaciones (CEI), diseño de fallo cerrado, flujo de trabajo de auditoría.
- Cuándo Usar Este Modelo: Diseñar nuevas instrucciones, revisar
#[derive(Accounts)], prepararse para auditoría externa, triaje de hallazgos o enseñar el modelo de amenazas de Solana. - Limitaciones / Compensaciones: Las restricciones de Anchor capturan muchas clases pero no las reglas de negocio;
UncheckedAccountyremaining_accountsreintroducen riesgo; las revisiones de seguridad no reemplazan el fuzzing o la revisión de diseño económico. - Temas Relacionados: Ataques de Validación de Cuentas, Comprobaciones de Firmante y Propietario, Ataques de PDA y Semillas, Ataques de Reinicialización, Aritmética y Desbordamiento, Riesgos de CPI y Reentrada, Flujo de Trabajo de Auditoría.
Fundamentos
Sealevel ejecuta programas como ejecutables sin estado. El estado duradero vive en cuentas que la transacción lista por adelantado. Los clientes (o atacantes) suministran esas cuentas, indicadores de firmante y indicadores de escritura. El runtime verifica que los firmantes declarados realmente firmaron y que solo el programa propietario puede escribir datos de cuenta. No sabe que "esta TokenAccount debe coincidir con vault.mint" o "solo la autoridad puede retirar".
Esa brecha es la superficie de ataque:
El cliente / atacante suministra:
program_id | datos de instrucción | AccountMeta[] (claves, is_signer, is_writable)
El runtime garantiza:
firmas coinciden con is_signer | bloqueos de is_writable | reglas de escritura del propietario | presupuesto de CU
Tu programa debe garantizar:
identidad correcta firmada | propietarios y tipos correctos | PDAs correctos | montos correctos | CPIs segurasFallar cerrado es la postura predeterminada: si la validación está incompleta, rechaza con un error tipificado. Nunca asumas "clientes honestos" o "nuestro frontend solo crea buenas transacciones". Los frontends y los indexadores no forman parte del límite de confianza; las comprobaciones en cadena sí lo son.
Anchor 0.32.1 codifica comprobaciones comunes (Signer, Account<T>, semillas, has_one, Program<T>). Eso no es una revisión completa. Aparecen lagunas con AccountInfo, UncheckedAccount, remaining_accounts, init_if_needed sin protección y las restricciones de reglas de negocio no se pueden expresar.
Modelo de amenazas para un programa típico de bóveda o mercado:
| Activo en riesgo | Fallo típico | Control de primera línea |
|---|---|---|
| Tokens / lamports | Firmante faltante o cuenta de token incorrecta | Signer, restricciones de menta/propietario |
| Autoridad de administrador / configuración | Reinicio o has_one no establecido | Inicialización única, autoridad almacenada + firmante |
| Contabilidad del protocolo | Desbordamiento, redondeo incorrecto | checked_*, intermedios u128 |
| Componibilidad | CPI a programa impostor | Lista blanca de ID de programa, CEI |
| Identidad del estado | PDA o confusión de tipos incorrectos | semillas + bump, discriminadores |
Mecánicas e Interacciones
Validación de cuentas y confusión de tipos
Los atacantes pasan cuentas que parecen utilizables: mismo tamaño, propiedad de tu programa o deserializables en el tipo incorrecto. La validación significa más que "se deserializa".
Lista de verificación por rol de cuenta:
- Propietario - programa esperado (
Account<T>para tus tipos;require_keys_eq!para SPL Token, Sysvar, etc.). - Tipo / discriminador - disco de cuenta de Anchor o constante nativa para que Vault nunca sea Usuario.
- Restricciones relacionales - la menta coincide con la bóveda, el propietario de la cuenta de token es el PDA de la bóveda, la configuración coincide con el mercado.
- Mutabilidad - solo las cuentas que pretendes cambiar son
mut. - PDA Canónico -
seeds+bumpalmacenado para que la dirección se derive, no se elija libremente.
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut, has_one = authority, has_one = mint)]
pub vault: Account<'info, Vault>,
#[account(
mut,
constraint = user_ata.mint == vault.mint @ ErrorCode::MintMismatch,
constraint = user_ata.owner == user.key() @ ErrorCode::WrongAtaOwner,
)]
pub user_ata: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub user: Signer<'info>,
pub token_program: Program<'info, Token>,
}UncheckedAccount y AccountInfo en bruto omiten las comprobaciones de propietario y disco hasta que las agregues. Trata cada campo de este tipo como un hallazgo hasta que se demuestre que está validado.
Comprobaciones de firmante y propietario
Firmante: cualquier ruta que mueva fondos, cambie la autoridad, cierre cuentas o actualice la configuración sensible necesita una firma criptográfica de la clave correcta (o un PDA firmado a través de invoke_signed con semillas controladas por el programa).
Propietario: solo el programa propietario puede reescribir datos. Leer una cuenta que tu programa no posee sin verificar el propietario es cómo entran la confusión de tipos y las mentas falsas. Para las cuentas de tokens, el propietario debe ser el programa Token o Token-2022 que pretendes; para tu estado, el propietario debe ser tu program_id.
Patrón crítico común: la bóveda almacena authority: Pubkey pero la instrucción nunca requiere authority como Signer o nunca la vincula con has_one.
Ataques de PDA y semillas
Los PDAs son identidad de autoridad y dirección. Las semillas débiles o ambiguas permiten a un atacante presentar un PDA diferente que tu programa aún "acepta".
Reglas generales:
- Incluye todas las dimensiones de identidad en las semillas (usuario, menta, ID de mercado, prefijo de rol).
- Almacena el bump canónico al inicializar; reutiliza
bump = account.bumpmás tarde (sin búsqueda ambigua de bump en rutas activas). - Firma las CPI solo con las mismas semillas utilizadas para crear el PDA.
- No permitas que los usuarios suministren bytes de semilla arbitrarios sin separación de dominio (
b"vault", etiquetas de versión).
#[account(
seeds = [b"vault", authority.key().as_ref(), mint.key().as_ref()],
bump = vault.bump,
)]
pub vault: Account<'info, Vault>,Reinicialización
La inicialización escribe discriminadores, autoridades y saldos. Volver a ejecutar la inicialización (o init_if_needed sin un indicador) puede restablecer el administrador al atacante o revivir una forma de cuenta cerrada.
Prefiere init de un solo uso para estado permanente. Si necesitas init_if_needed, requiere !is_initialized (o equivalente) antes de escribir campos de autoridad, y nunca reabras cuentas cerradas sin una ruta deliberada y auditada. Al cerrar, pon a cero los datos / disco y transfiere los lamports para que la cuenta no pueda tratarse como estado activo.
Aritmética y desbordamiento
Las compilaciones de lanzamiento de BPF se envuelven en +, -, * simples. Las matemáticas de bóveda, la acuñación de acciones, la acumulación de tarifas y las fórmulas de AMM necesitan checked_* (u operaciones de saturación donde sea intencional) y a menudo intermedios u128 antes de volver a convertir a u64.
El redondeo debe favorecer al protocolo en las mentas y redenciones dirigidas al usuario para que el polvo no pueda ser explotado para obtener valor gratuito. La división por cero y los casos extremos de piscina vacía son errores de seguridad, no meras experiencias de usuario.
Riesgo de estilo CPI y reentrada
La invocación entre programas extiende la confianza a otro programa. Riesgos:
- ID de programa incorrecto - "programa de token" impostor que no hace nada o roba.
- Cuentas restantes no validadas - cuentas de devolución de llamada controladas por el atacante.
- Estado después de CPI - el llamado vuelve a entrar en tu programa (o uno similar) mientras los saldos aún parecen pre-actualización.
Mitigaciones: lista blanca de llamados con Program<'info, T> o require_keys_eq!; actualiza el estado del programa antes de la CPI externa (CEI); evita las devoluciones de llamada no confiables a menos que diseñes comprobaciones explícitas de deuda al estilo de préstamos rápidos; pasa solo las cuentas que el llamado necesita.
vault.balance = vault.balance.checked_sub(amount).ok_or(ErrorCode::Overflow)?;
// efectos primero, luego interacción
token::transfer(cpi_ctx, amount)?;Solana no es idéntica a la reentrada de EVM, pero un orden de llamada incorrecto y objetivos de CPI no confiables aún pierden fondos.
Flujo de trabajo de auditoría (columna vertebral)
El trabajo de seguridad es tanto un proceso como código:
- Congela una etiqueta RC, bloquea dependencias (Anchor 0.32.1, pines de Agave/CLI), documenta el modelo de amenazas.
- Mapea internamente cada instrucción a las clases del Catálogo de Ataques de Sealevel (firmante, propietario, sustitución, PDA, reinicio, matemáticas, CPI, cierre, oráculo).
- Pruebas negativas en LiteSVM / pruebas de Anchor: firmante incorrecto, menta incorrecta, programa incorrecto, doble inicialización, cantidades de desbordamiento.
- Fuzzing de análisis y rutas con muchas matemáticas cuando la complejidad lo justifique.
- Compilación verificable para que los auditores coincidan el código fuente con el hash en cadena.
- Auditoría externa + ciclo de corrección; diff de nuevo después de cada cambio en
Accountso aritmética. - Recompensa por errores y plan de actualización/reversión para riesgo residual.
Consideraciones Avanzadas y Aplicaciones
| Capa de control | Lo que captura | Lo que omite | Cuándo apoyarse más |
|---|---|---|---|
| Tipos de cuenta de Anchor | Propietario + disco para Account<T>, muchas restricciones de PDA | Reglas de negocio, rutas no comprobadas | Predeterminado para todas las cuentas de estado |
require_* manual | Restricciones relacionales y económicas | Fácil de omitir en nuevas ixs | Cada revisión de nuevo manejador |
| CEI + lista blanca de programas | Robos clásicos de CPI / programas impostores | Errores de diseño económico | Cualquier movimiento de token o devolución de llamada |
| Matemáticas comprobadas + pruebas de propiedades | Desbordamientos y casos extremos de matemáticas de acciones | Juegos de oráculos y MEV | Bóvedas, AMMs, préstamos |
| Auditoría externa + recompensa | Ojos frescos, clases de catálogo conocidas | Ataques de presupuesto infinito, futuras actualizaciones | Pre-lanzamiento y actualizaciones importantes |
Oráculos son entradas no confiables: verifica el propietario de la fuente, la obsolescencia y la confianza; falla cerrado con datos incorrectos.
Token-2022 extensiones (hooks, delegados permanentes) cambian quién puede mover tokens. Fija el ID del programa de token; no asumas semántica clásica de SPL Token.
Cierre y alquiler: requiere el firmante y destino correctos; pon a cero los datos para que las cuentas no puedan revivir bajo comprobaciones débiles. Prefiere el close = de Anchor con restricciones.
Autoridad de actualización es la raíz. Multisig o gobernanza, compilaciones verificables y opcionalmente inmutabilidad después de la madurez son seguridad operativa.
Clientes (@solana/kit 7.0.0, billeteras) ayudan solo a usuarios honestos. Los programas defienden contra todos los demás.
En Agave 4.1.1 (CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1), ejercita conjuntos de cuentas adversarias en LiteSVM o validadores locales. Trata cada nueva instrucción como una nueva superficie de ataque.
Conceptos Erróneos Comunes
- "El runtime valida las reglas de mi bóveda." Valida firmas, bloqueos, propietarios para escrituras y CU. Las reglas del protocolo son solo tuyas.
- "Anchor significa que somos seguros." Anchor codifica comprobaciones comunes; las cuentas no comprobadas, las restricciones incorrectas y los errores de matemáticas aún se envían.
- "Si se deserializa, la cuenta está bien." La deserialización no es vinculación de menta, autoridad o identidad de PDA.
- "Los PDAs no se pueden falsificar." Semillas incorrectas o incompletas producen un PDA válido diferente bajo tu programa. Las semillas definen la política de seguridad.
- "init_if_needed siempre es seguro." Sin un indicador de inicialización (o equivalente), reinicializar restablece campos críticos.
- "Rust previene el desbordamiento en cadena." El envoltorio de compilación de lanzamiento es real; usa matemáticas comprobadas para rutas de valor.
- "La CPI a Token siempre es segura." Solo si el ID del programa es el programa Token/Token-2022 real y las cuentas coinciden con las restricciones de menta y propietario.
- "Arreglaremos la seguridad en la auditoría." Las auditorías son muestreo. Diseña primero el fallo cerrado; las auditorías detectan errores residuales.
- "Las cuentas de solo lectura no pueden hacernos daño." Las configuraciones falsas, los oráculos y las mentas a menudo son de solo lectura pero controlan completamente los resultados económicos.
- "Cerrar una cuenta termina todo el riesgo." El cierre incompleto o el destino incorrecto crean problemas de robo de alquiler y reactivación.
Preguntas Frecuentes
¿Cuál es la superficie de ataque de Sealevel para un programa?
Todo lo que el llamador puede elegir: qué cuentas y programas aparecen, quién firma, los datos de la instrucción y los objetivos de CPI. El runtime no aplica tus invariantes de menta, autoridad o saldo. Ver Catálogo de Ataques de Sealevel.
¿Por qué las comprobaciones de firmante faltantes son tan comunes y graves?
Sin un Signer requerido (o ruta invoke_signed de PDA), cualquier clave de cuenta puede ser nombrada como "autoridad" mientras que solo el pagador de la tarifa firma realmente. Eso permite retiros no autorizados y toma de control de administrador. Ver Comprobaciones de Firmante y Propietario.
¿Qué es la sustitución de cuentas?
Pasar una bóveda, cuenta de token, menta o configuración diferente a la que pretende el protocolo, a menudo todavía propiedad de un programa plausible. Solución con restricciones relacionales (has_one, igualdad de menta, semillas). Ver Ataques de Validación de Cuentas.
¿Cómo funcionan los ataques de semillas de PDA?
Si las semillas omiten campos de identidad o los bumps no son canónicos, los atacantes derivan PDAs alternativos que tu instrucción aún acepta, suplantando bóvedas o escrows. Almacena bumps y fija listas completas de semillas. Ver Ataques de PDA y Semillas.
¿Cuándo es posible la reinicialización?
Cuando la inicialización puede ejecutarse nuevamente en estado activo: init_if_needed sin guardas, discriminadores faltantes o cuentas cerradas que se recrean bajo comprobaciones débiles. Ver Ataques de Reinicialización.
¿Solana necesita aritmética comprobada?
Sí, para rutas de valor. La aritmética simple puede envolverse en cadena; las fórmulas de acciones y swaps deben usar checked_* y un redondeo cuidadoso. Ver Aritmética y Desbordamiento.
¿Qué es CEI en un contexto de CPI de Solana?
Comprobaciones-efectos-interacciones: valida, actualiza el estado de tu programa, luego llama externamente. Limita las lecturas al estilo de reentrada de saldos obsoletos durante CPIs no confiables o recursivas. Ver Riesgos de CPI y Reentrada.
¿Cómo valido un programa de destino de CPI?
Usa Program<'info, Token> (o tu tipo conocido) o require_keys_eq! contra un ID constante. Nunca hagas CPI a una clave de programa proporcionada por el usuario sin una lista blanca explícita.
¿Son Token y Token-2022 intercambiables en términos de seguridad?
No. Diferentes IDs de programa y comportamientos de extensión (hooks, tarifas de transferencia, delegados) cambian quién puede mover fondos. Fija el programa y prueba las mentas habilitadas para extensiones.
¿Qué debe cubrir una pre-auditoría interna?
Cada instrucción mapeada a clases de catálogo, pruebas unitarias negativas, pines de dependencias, notas de compilación verificables y riesgos residuales conocidos. Ver Flujo de Trabajo de Auditoría.
¿Significa "fallar cerrado" rechazar todas las cuentas desconocidas?
Significa rechazar cuando falta o es inconsistente la prueba requerida: firmante incorrecto, propietario, menta, PDA, oráculo obsoleto o programa no permitido en lista blanca. Las cuentas opcionales aún necesitan reglas explícitas si están presentes.
¿Cómo interactúa la seguridad con el paralelismo de Sealevel?
La seguridad es corrección por instrucción; el paralelismo trata sobre conjuntos de bloqueos. No debilites la validación solo para pasar menos cuentas.
¿Debería la autoridad de actualización ser una única clave activa?
Evita para valor de producción en riesgo. Prefiere multisig o gobernanza, monitorea las actualizaciones y considera congelar la autoridad después de la madurez.
¿Dónde encajan los oráculos en la seguridad del programa?
Como entradas no confiables. Verifica el propietario de la fuente, la obsolescencia y la confianza antes de usar precios. Falla cerrado con datos faltantes u obsoletos.
¿Cuál es la forma más rápida de revisar una nueva instrucción?
Lee Accounts (firmantes, propietarios, semillas, relaciones, init vs mut, IDs de programa), luego el orden del manejador (matemáticas, escrituras, CPI), luego pruebas negativas para cada modo de fallo.
Relacionados
- Conceptos Básicos de Seguridad - modelo de amenazas y patrones de inicio
- Comprobaciones de Firmante y Propietario - lo esencial del control de acceso
- Ataques de Validación de Cuentas - sustitución y confusión de tipos
- Ataques de PDA y Semillas - semillas, bumps, suplantación
- Ataques de Reinicialización - guardas de init y init_if_needed
- Aritmética y Desbordamiento - matemáticas comprobadas y redondeo
- Riesgos de CPI y Reentrada - listas blancas y CEI
- Catálogo de Ataques de Sealevel - lista de verificación de clases clásicas
- Flujo de Trabajo de Auditoría - congelar, probar, auditar, recompensa
- Mejores Prácticas - lista de verificación de sección
Versiones de la pila: Esta página fue escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1 y @solana/kit 7.0.0.