frames,jar,passes: verifier-driven hardening from real jars
Proven by paranoid runs over lorep (17 classes, fully green), LoVisual (4081 classes) and oc2r (1278 classes) with per-class java -Xverify:all, each fix first reproduced as a red test: - merge: Top absorbs instead of Top-wins (javac declares Top for slots unwritten on some inflow; precise types reject those edges). Regression: loop_head_keeps_top_for_loop_local. - emit: collapse [Long|Double, Top] pairs on operand stacks (javac writes one entry; the pair is rejected). Regression: wide_stack_join_emits_single_entries. - xfer ArrayLoad: push the wide pair, fixing lcmp underflow. Regression: array_load_wide_pushes_pair. - xfer <init>: convert only the receiver's own allocation site instead of every Uninitialized (nested construction clobbered the outer object). Regression: nested_construction_keeps_outer. - xfer ANewArray: array operands gain one dimension ([B] -> [[B) instead of an illegal L-wrap ([L[B;). Covered by the same test. - jar load: duplicate class names (multi-release module-info twins) keep the first occurrence instead of crashing save. Test: duplicate_class_names_keep_first. - strings/constants: skip methods past MAX_METHOD_INSNS (8000) so giants cannot overflow the 64KB writer ceiling. Tests: oversized_methods_are_skipped in both passes. - merge errors now name both types being merged. Also: em-dash removed repo-wide (724 spots, rule added to todo.md), allow-lints only in tests (scratch example deleted), code comments migrated to English in every file this change touches.
This commit is contained in:
parent
2172206e83
commit
139aea9a8f
96 changed files with 1053 additions and 831 deletions
|
|
@ -4,7 +4,7 @@
|
|||
//! Вход собирается `ByteBuffer`, чтобы не материализовывать 26 байт
|
||||
//! поштучно: `allocate.putLong(master).put(domain).putLong(salt).array()`.
|
||||
//!
|
||||
//! Здесь же — строитель тела метода и короткие эмиссии (общие для всех
|
||||
//! Здесь же - строитель тела метода и короткие эмиссии (общие для всех
|
||||
//! ветвей декодера).
|
||||
|
||||
use noxis_classfile::{Arith, BranchCond, Cond, Const, Insn, InvokeKind, Label, Prim, SlotKind};
|
||||
|
|
@ -21,7 +21,7 @@ impl Gen {
|
|||
Self {
|
||||
insns: Vec::new(),
|
||||
labels: 0,
|
||||
// Слоты 0 (`cipher`) и 1 (`salt`) — параметры метода.
|
||||
// Слоты 0 (`cipher`) и 1 (`salt`) - параметры метода.
|
||||
slots: 2,
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ impl Gen {
|
|||
label
|
||||
}
|
||||
|
||||
/// Занять слоты (`wide` — пара под `long`).
|
||||
/// Занять слоты (`wide` - пара под `long`).
|
||||
pub(super) fn alloc(&mut self, wide: bool) -> u16 {
|
||||
let slot = self.slots;
|
||||
self.slots += if wide { 2 } else { 1 };
|
||||
|
|
@ -134,7 +134,7 @@ pub(super) fn emit_kdf(asm: &mut Gen, master: u16, domain: &str) -> u16 {
|
|||
|
||||
/// Собрать вход `master || domain || salt` в слот `input`.
|
||||
///
|
||||
/// Rust считает FNV по little-endian байтам, а `putLong` кладёт big-endian —
|
||||
/// Rust считает FNV по little-endian байтам, а `putLong` кладёт big-endian -
|
||||
/// поэтому оба `long` разворачиваются (`Long.reverseBytes`).
|
||||
fn build_input(asm: &mut Gen, master: u16, domain: &str, input: u16) {
|
||||
asm.emit(c_int(16 + domain.len() as i32));
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
//! static String d(String cipher, int salt)
|
||||
//! ```
|
||||
//!
|
||||
//! - `cipher` — base64 шифротекста (схема выбирается по salt: `scheme = all[salt % n]`);
|
||||
//! - `salt` — индекс строки, по нему детерминированно выводятся key и nonce;
|
||||
//! - `cipher` - base64 шифротекста (схема выбирается по salt: `scheme = all[salt % n]`);
|
||||
//! - `salt` - индекс строки, по нему детерминированно выводятся key и nonce;
|
||||
//! - мастер-секрет **не передаётся на вызове** и не лежит в одной константе:
|
||||
//! он собирается из двух частей (`k1 ^ k2`) прямо в декодере.
|
||||
//!
|
||||
|
|
@ -63,7 +63,7 @@ pub fn decoder_class(
|
|||
emit_dispatch(&mut asm, &parsed, key, nonce, data, len);
|
||||
emit_epilogue(&mut asm, data);
|
||||
let code = Code {
|
||||
// Точный пик — 10 слотов (замер 08.10.2026); запас без вреда:
|
||||
// Точный пик - 10 слотов (замер 08.10.2026); запас без вреда:
|
||||
// лимит обязан лишь покрывать фактическую глубину.
|
||||
max_stack: 12,
|
||||
max_locals: asm.slots,
|
||||
|
|
@ -88,7 +88,7 @@ pub fn decoder_owner(seed_name: &str) -> String {
|
|||
}
|
||||
|
||||
/// Пролог: `master = k1 ^ k2`; возвращает слот пары. Ни одна часть не равна
|
||||
/// мастер-секрету (вторая — `master ^ k1`, первая — потоковая).
|
||||
/// мастер-секрету (вторая - `master ^ k1`, первая - потоковая).
|
||||
fn emit_master(asm: &mut Gen, master: u64) -> u16 {
|
||||
use noxis_core::Rng;
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ fn emit_len(asm: &mut Gen, data: u16) -> u16 {
|
|||
slot
|
||||
}
|
||||
|
||||
/// Диспетчер схем: `salt % n` лестницей `if_icmpne` (n == 1 — без диспетчера).
|
||||
/// Диспетчер схем: `salt % n` лестницей `if_icmpne` (n == 1 - без диспетчера).
|
||||
fn emit_dispatch(asm: &mut Gen, schemes: &[Scheme], key: u16, nonce: u16, data: u16, len: u16) {
|
||||
if schemes.len() == 1 {
|
||||
dispatch_branch(asm, schemes[0], key, nonce, data, len);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use noxis_classfile::{Arith, ArrayKind, BranchCond, Cond, Convert, Insn, Prim, S
|
|||
|
||||
use super::kdf::{Gen, c_int, c_long, load, store};
|
||||
|
||||
/// Расшифровать `data` на месте; `len` — длина массива.
|
||||
/// Расшифровать `data` на месте; `len` - длина массива.
|
||||
pub(super) fn emit_xor(asm: &mut Gen, key: u16, nonce: u16, data: u16, len: u16) {
|
||||
let liga = Slots::alloc(asm);
|
||||
emit_seed(asm, key, nonce, &liga);
|
||||
|
|
@ -76,7 +76,7 @@ fn emit_states(asm: &mut Gen, slots: &Slots) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Один `splitmix64`: `sm += PHI`, финализатор — в `out`.
|
||||
/// Один `splitmix64`: `sm += PHI`, финализатор - в `out`.
|
||||
fn splitmix(asm: &mut Gen, sm: u16, out: u16) {
|
||||
const PHI: i64 = 0x9E37_79B9_7F4A_7C15u64 as i64;
|
||||
const C1: i64 = 0xBF58_476D_1CE4_E5B9u64 as i64;
|
||||
|
|
@ -105,7 +105,7 @@ fn finalize(asm: &mut Gen, amount: i32, constant: i64) {
|
|||
asm.emit(long_op(Arith::Mul));
|
||||
}
|
||||
|
||||
/// Цикл по чанкам: свежий `next_u64` на 8 байт, внутренний — по байтам.
|
||||
/// Цикл по чанкам: свежий `next_u64` на 8 байт, внутренний - по байтам.
|
||||
///
|
||||
/// Без `skip`-джойна: стрим всегда свеж на входе во внутренний цикл.
|
||||
fn emit_loop(asm: &mut Gen, data: u16, len: u16, slots: &Slots) {
|
||||
|
|
@ -188,7 +188,7 @@ fn emit_next(asm: &mut Gen, slots: &Slots) {
|
|||
asm.emit(c_int(17));
|
||||
asm.emit(shift(Arith::Shl));
|
||||
asm.emit(store(slots.tmp_c, SlotKind::Long));
|
||||
// s2 ^= s0; s3 ^= s1; s1 ^= s2; s0 ^= s3 — строго по порядку.
|
||||
// s2 ^= s0; s3 ^= s1; s1 ^= s2; s0 ^= s3 - строго по порядку.
|
||||
xor_into(asm, s[2], s[0]);
|
||||
xor_into(asm, s[3], s[1]);
|
||||
xor_into(asm, s[1], s[2]);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
//! Ветвь `xtea-ctr`: XTEA в режиме счётчика, побайтовый XOR на месте.
|
||||
//!
|
||||
//! Зеркало `scheme::xtea_ctr` побитово: ключ — четыре `u32` из key/nonce,
|
||||
//! счётчик — номер чанка, все правые сдвиги — логические (`u32` в Rust).
|
||||
//! Зеркало `scheme::xtea_ctr` побитово: ключ - четыре `u32` из key/nonce,
|
||||
//! счётчик - номер чанка, все правые сдвиги - логические (`u32` в Rust).
|
||||
|
||||
use noxis_classfile::{Arith, ArrayKind, BranchCond, Cond, Convert, Insn, Prim, SlotKind};
|
||||
|
||||
use super::kdf::{Gen, c_int, load, store};
|
||||
|
||||
/// XTEA delta — знаковое представление для `ldc_w`.
|
||||
/// XTEA delta - знаковое представление для `ldc_w`.
|
||||
const DELTA: i32 = 0x9E37_79B9u32 as i32;
|
||||
|
||||
/// Расшифровать `data` на месте; `len` — длина массива.
|
||||
/// Расшифровать `data` на месте; `len` - длина массива.
|
||||
pub(super) fn emit_xtea(asm: &mut Gen, key: u16, nonce: u16, data: u16, len: u16) {
|
||||
let slots = Slots::alloc(asm);
|
||||
emit_key_array(asm, key, nonce, &slots);
|
||||
|
|
@ -168,7 +168,7 @@ fn mixing(asm: &mut Gen, dst: u16, src: u16, key_shift: i32, slots: &Slots) {
|
|||
asm.emit(store(dst, SlotKind::Int));
|
||||
}
|
||||
|
||||
/// XOR 8 байт чанка со стримом `(v0, v1)`; хвост короче 8 — частично.
|
||||
/// XOR 8 байт чанка со стримом `(v0, v1)`; хвост короче 8 - частично.
|
||||
fn emit_xor_bytes(asm: &mut Gen, data: u16, len: u16, slots: &Slots) {
|
||||
asm.emit(c_int(0));
|
||||
asm.emit(store(slots.index, SlotKind::Int));
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
//! Схемы шифрования строк: пер-строчный ключ, nonce и несколько алгоритмов.
|
||||
//!
|
||||
//! Требования (см. `info/evo-weaknesses.md` и `docs/techniques.md`):
|
||||
//! - **разные схемы между строками** — один скрипт не покрывает весь jar;
|
||||
//! - **ключ не рядом с шифротекстом** — на вызов передаётся только salt,
|
||||
//! мастер-секрет живёт в рантайм-декодере (сборка мастер-секрета — P2);
|
||||
//! - **nonce уникален на строку** — keystream не повторяется;
|
||||
//! - **MAC** — повреждение/подмена шифротекста ловится, а не молча портит текст.
|
||||
//! - **разные схемы между строками** - один скрипт не покрывает весь jar;
|
||||
//! - **ключ не рядом с шифротекстом** - на вызов передаётся только salt,
|
||||
//! мастер-секрет живёт в рантайм-декодере (сборка мастер-секрета - P2);
|
||||
//! - **nonce уникален на строку** - keystream не повторяется;
|
||||
//! - **MAC** - повреждение/подмена шифротекста ловится, а не молча портит текст.
|
||||
|
||||
use noxis_core::{Rng, derive_seed};
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ impl Scheme {
|
|||
pub struct Blob {
|
||||
/// base64 шифротекста.
|
||||
pub cipher: String,
|
||||
/// 32-битный MAC по ключу от plaintext — ловит подмену/порчу.
|
||||
/// 32-битный MAC по ключу от plaintext - ловит подмену/порчу.
|
||||
pub tag: u32,
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ pub fn string_key(master: u64, salt: i32) -> u64 {
|
|||
derive_seed(master, "string/key", u64::from(salt as u32))
|
||||
}
|
||||
|
||||
/// Nonce строки: детерминированный от salt, но другой домен — keystream не
|
||||
/// Nonce строки: детерминированный от salt, но другой домен - keystream не
|
||||
/// пересекается даже при совпадении ключей.
|
||||
pub fn string_nonce(master: u64, salt: i32) -> u64 {
|
||||
derive_seed(master, "string/nonce", u64::from(salt as u32))
|
||||
|
|
@ -123,7 +123,7 @@ fn xor_splitmix(key: u64, nonce: u64, data: &mut [u8]) {
|
|||
}
|
||||
}
|
||||
|
||||
/// XTEA в режиме счётчика: keystream = XTEA(k, counter), XOR — на месте.
|
||||
/// XTEA в режиме счётчика: keystream = XTEA(k, counter), XOR - на месте.
|
||||
fn xtea_ctr(key: u64, nonce: u64, data: &mut [u8]) {
|
||||
let k = [
|
||||
key as u32,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue