Release 2.1.3: Fix disk backup corruption, solid PQ encryption, block I/O, and format mismatches

- Corrected LZHP prediction encoding in disk backups to prevent data corruption
- Disabled spurious SOLID flag for per-block disk archives
- Shared write_enc_header() across all encryption paths to eliminate format mismatches
- Enabled solid compression with PQ encryption support
- Updated block device restore to use O_SYNC + fsync/sync
- Improved Termux/Android host detection for safer builds
- Made zupt_w8(), zupt_w16le(), zupt_w64le() non-static for shared use
This commit is contained in:
Cristian Cezar Moisés 2026-04-11 15:59:59 -03:00
commit 28b2744c67
39 changed files with 324 additions and 330 deletions

View file

@ -249,12 +249,94 @@ void zupt_collect_files(zupt_filelist_t *fl, const char *path, const char *base)
* WRITE / READ HELPERS (LE-safe, error-checked)
* */
static int w8(FILE*f,uint8_t v){return fwrite(&v,1,1,f)==1?0:-1;}
static int w16le(FILE*f,uint16_t v){uint8_t b[2];zupt_le16_put(b,v);return fwrite(b,1,2,f)==2?0:-1;}
static int w64le(FILE*f,uint64_t v){uint8_t b[8];zupt_le64_put(b,v);return fwrite(b,1,8,f)==8?0:-1;}
int zupt_w8(FILE*f,uint8_t v){return fwrite(&v,1,1,f)==1?0:-1;}
int zupt_w16le(FILE*f,uint16_t v){uint8_t b[2];zupt_le16_put(b,v);return fwrite(b,1,2,f)==2?0:-1;}
int zupt_w64le(FILE*f,uint64_t v){uint8_t b[8];zupt_le64_put(b,v);return fwrite(b,1,8,f)==8?0:-1;}
static int r16le(FILE*f,uint16_t*v){uint8_t b[2];if(fread(b,1,2,f)!=2)return -1;*v=zupt_le16_get(b);return 0;}
static int r64le(FILE*f,uint64_t*v){uint8_t b[8];if(fread(b,1,8,f)!=8)return -1;*v=zupt_le64_get(b);return 0;}
/* Aliases for internal use (backward compat with existing code) */
#define w8 zupt_w8
#define w16le zupt_w16le
#define w64le zupt_w64le
/* ═══════════════════════════════════════════════════════════════════
* SHARED ENCRYPTION HEADER WRITER
*
* Used by BOTH zupt_compress_files() and zupt_disk_backup().
* Writes the encryption header block and updates hdr.encryption_header_off.
* After return, the file position is at the end (ready for data blocks).
* */
zupt_error_t write_enc_header(FILE *out, zupt_archive_header_t *hdr,
zupt_options_t *opts) {
hdr->encryption_header_off = (uint64_t)ftello(out);
if (opts->pq_mode) {
/* ─── PQ HYBRID MODE ─── */
hdr->global_flags |= ZUPT_FLAG_PQ_HYBRID;
uint8_t enc_hdr_buf[1200];
size_t enc_hdr_len = 0;
if (!opts->quiet)
fprintf(stderr, " Post-quantum key encapsulation (ML-KEM-768 + X25519)...\n");
if (zupt_hybrid_encrypt_init(&opts->keyring, opts->keyfile,
enc_hdr_buf, &enc_hdr_len) != 0) {
fprintf(stderr, "Error: PQ hybrid key encapsulation failed.\n");
return ZUPT_ERR_AUTH_FAIL;
}
zupt_w8(out, ZUPT_BLOCK_MAGIC_0); zupt_w8(out, ZUPT_BLOCK_MAGIC_1);
zupt_w8(out, ZUPT_BLOCK_ENC_HEADER);
zupt_w16le(out, ZUPT_CODEC_STORE); zupt_w16le(out, 0);
zupt_write_varint(out, enc_hdr_len);
zupt_write_varint(out, enc_hdr_len);
zupt_w64le(out, zupt_xxh64(enc_hdr_buf, enc_hdr_len, 0));
if (fwrite(enc_hdr_buf, 1, enc_hdr_len, out) != enc_hdr_len)
return ZUPT_ERR_IO;
/* Re-write header with PQ flag */
fseeko(out, 0, SEEK_SET);
if (fwrite(hdr, sizeof(*hdr), 1, out) != 1) return ZUPT_ERR_IO;
fseeko(out, 0, SEEK_END);
if (!opts->quiet)
fprintf(stderr, " Encryption: PQ Hybrid (ML-KEM-768 + X25519) + AES-256-CTR + HMAC-SHA256\n\n");
} else {
/* ─── PASSWORD MODE (PBKDF2) ─── */
uint8_t salt[ZUPT_SALT_SIZE], nonce[ZUPT_NONCE_SIZE];
zupt_random_bytes(salt, ZUPT_SALT_SIZE);
zupt_random_bytes(nonce, ZUPT_NONCE_SIZE);
if (!opts->quiet)
fprintf(stderr, " Deriving encryption key (PBKDF2-SHA256, %d iterations)...\n",
ZUPT_KDF_ITERATIONS);
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, ZUPT_KDF_ITERATIONS);
uint8_t enc_hdr[53];
enc_hdr[0] = ZUPT_ENC_PBKDF2;
memcpy(enc_hdr + 1, salt, 32);
memcpy(enc_hdr + 33, nonce, 16);
uint32_t iter = ZUPT_KDF_ITERATIONS;
memcpy(enc_hdr + 49, &iter, 4);
zupt_w8(out, ZUPT_BLOCK_MAGIC_0); zupt_w8(out, ZUPT_BLOCK_MAGIC_1);
zupt_w8(out, ZUPT_BLOCK_ENC_HEADER);
zupt_w16le(out, ZUPT_CODEC_STORE); zupt_w16le(out, 0);
zupt_write_varint(out, 53); zupt_write_varint(out, 53);
zupt_w64le(out, zupt_xxh64(enc_hdr, 53, 0));
if (fwrite(enc_hdr, 1, 53, out) != 53) return ZUPT_ERR_IO;
fseeko(out, 0, SEEK_SET);
if (fwrite(hdr, sizeof(*hdr), 1, out) != 1) return ZUPT_ERR_IO;
fseeko(out, 0, SEEK_END);
if (!opts->quiet)
fprintf(stderr, " Encryption: AES-256-CTR + HMAC-SHA256 (Encrypt-then-MAC)\n\n");
}
return ZUPT_OK;
}
static void ensure_dirs(const char *path) {
char tmp[ZUPT_MAX_PATH]; strncpy(tmp, path, sizeof(tmp)-1); tmp[sizeof(tmp)-1]='\0';
for (char *p=tmp+1;*p;p++)
@ -330,63 +412,8 @@ zupt_error_t zupt_compress_files(const char *output_path,
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
if (opts->encrypt) {
hdr.encryption_header_off = safe_ftello(out);
if (opts->pq_mode) {
/* ─── PQ HYBRID MODE ─── */
if (hdr.global_flags & ZUPT_FLAG_PQ_HYBRID) {} /* already set */
hdr.global_flags |= ZUPT_FLAG_PQ_HYBRID;
uint8_t enc_hdr_buf[1200]; /* enc_type(1) + ct(1088) + eph_pk(32) + nonce(16) = 1137 */
size_t enc_hdr_len = 0;
if (!opts->quiet) fprintf(stderr, " Post-quantum key encapsulation (ML-KEM-768 + X25519)...\n");
if (zupt_hybrid_encrypt_init(&opts->keyring, opts->keyfile, enc_hdr_buf, &enc_hdr_len) != 0) {
fprintf(stderr, "Error: PQ hybrid key encapsulation failed.\n");
fclose(out); return ZUPT_ERR_AUTH_FAIL;
}
w8(out, ZUPT_BLOCK_MAGIC_0); w8(out, ZUPT_BLOCK_MAGIC_1);
w8(out, ZUPT_BLOCK_ENC_HEADER);
w16le(out, ZUPT_CODEC_STORE); w16le(out, 0);
zupt_write_varint(out, enc_hdr_len); zupt_write_varint(out, enc_hdr_len);
w64le(out, zupt_xxh64(enc_hdr_buf, enc_hdr_len, 0));
if (fwrite(enc_hdr_buf, 1, enc_hdr_len, out) != enc_hdr_len) write_err = 1;
fseeko(out, 0, SEEK_SET);
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
fseeko(out, 0, SEEK_END);
if (!opts->quiet) fprintf(stderr, " Encryption: PQ Hybrid (ML-KEM-768 + X25519) + AES-256-CTR + HMAC-SHA256\n\n");
} else {
/* ─── PASSWORD MODE (PBKDF2, unchanged from v0.5.1) ─── */
uint8_t salt[ZUPT_SALT_SIZE], nonce[ZUPT_NONCE_SIZE];
zupt_random_bytes(salt, ZUPT_SALT_SIZE);
zupt_random_bytes(nonce, ZUPT_NONCE_SIZE);
if (!opts->quiet) fprintf(stderr, " Deriving encryption key (PBKDF2-SHA256, %d iterations)...\n", ZUPT_KDF_ITERATIONS);
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, ZUPT_KDF_ITERATIONS);
/* enc_type prefix for backward compat detection */
uint8_t enc_hdr[53]; /* enc_type(1) + salt(32) + nonce(16) + iter(4) */
enc_hdr[0] = ZUPT_ENC_PBKDF2;
memcpy(enc_hdr + 1, salt, 32);
memcpy(enc_hdr + 33, nonce, 16);
uint32_t iter = ZUPT_KDF_ITERATIONS;
memcpy(enc_hdr + 49, &iter, 4);
w8(out, ZUPT_BLOCK_MAGIC_0); w8(out, ZUPT_BLOCK_MAGIC_1);
w8(out, ZUPT_BLOCK_ENC_HEADER);
w16le(out, ZUPT_CODEC_STORE); w16le(out, 0);
zupt_write_varint(out, 53); zupt_write_varint(out, 53);
w64le(out, zupt_xxh64(enc_hdr, 53, 0));
if (fwrite(enc_hdr, 1, 53, out) != 53) write_err = 1;
fseeko(out, 0, SEEK_SET);
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
fseeko(out, 0, SEEK_END);
if (!opts->quiet) fprintf(stderr, " Encryption: AES-256-CTR + HMAC-SHA256 (Encrypt-then-MAC)\n\n");
}
zupt_error_t enc_err = write_enc_header(out, &hdr, opts);
if (enc_err != ZUPT_OK) { fclose(out); return enc_err; }
}
zupt_index_entry_t *index = (zupt_index_entry_t*)calloc((size_t)num_files, sizeof(zupt_index_entry_t));
@ -774,25 +801,8 @@ zupt_error_t zupt_compress_solid(const char *output_path,
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
if (opts->encrypt) {
hdr.encryption_header_off = safe_ftello(out);
uint8_t salt[ZUPT_SALT_SIZE], nonce[ZUPT_NONCE_SIZE];
zupt_random_bytes(salt, ZUPT_SALT_SIZE);
zupt_random_bytes(nonce, ZUPT_NONCE_SIZE);
if (!opts->quiet) fprintf(stderr, " Deriving encryption key...\n");
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, ZUPT_KDF_ITERATIONS);
uint8_t enc_hdr[52];
memcpy(enc_hdr, salt, 32); memcpy(enc_hdr+32, nonce, 16);
uint32_t iter = ZUPT_KDF_ITERATIONS; memcpy(enc_hdr+48, &iter, 4);
w8(out, ZUPT_BLOCK_MAGIC_0); w8(out, ZUPT_BLOCK_MAGIC_1);
w8(out, ZUPT_BLOCK_ENC_HEADER);
w16le(out, ZUPT_CODEC_STORE); w16le(out, 0);
zupt_write_varint(out, 52); zupt_write_varint(out, 52);
w64le(out, zupt_xxh64(enc_hdr, 52, 0));
if (fwrite(enc_hdr, 1, 52, out) != 52) write_err = 1;
fseeko(out, 0, SEEK_SET);
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
fseeko(out, 0, SEEK_END);
if (!opts->quiet) fprintf(stderr, " Encryption: AES-256-CTR + HMAC-SHA256 (Encrypt-then-MAC)\n\n");
zupt_error_t enc_err = write_enc_header(out, &hdr, opts);
if (enc_err != ZUPT_OK) { fclose(out); return enc_err; }
}
zupt_index_entry_t *index = (zupt_index_entry_t*)calloc((size_t)num_files, sizeof(zupt_index_entry_t));
@ -1036,7 +1046,7 @@ static zupt_error_t read_footer(FILE *f, zupt_footer_t *ft) {
return ZUPT_OK;
}
static zupt_error_t read_block(FILE *f, zupt_block_t *b) {
zupt_error_t read_block(FILE *f, zupt_block_t *b) {
uint8_t m[2];
if (fread(m,1,2,f)!=2) return ZUPT_ERR_IO;
if (m[0]!=ZUPT_BLOCK_MAGIC_0||m[1]!=ZUPT_BLOCK_MAGIC_1) return ZUPT_ERR_CORRUPT;
@ -1058,7 +1068,7 @@ static zupt_error_t read_block(FILE *f, zupt_block_t *b) {
return ZUPT_OK;
}
static zupt_error_t decompress_block(const zupt_block_t *b, const zupt_keyring_t *kr,
zupt_error_t decompress_block(const zupt_block_t *b, const zupt_keyring_t *kr,
uint64_t block_seq, uint8_t **out, size_t *olen) {
const uint8_t *comp_data = b->payload;
size_t comp_len = (size_t)b->compressed_size;
@ -1147,7 +1157,7 @@ done:
return ZUPT_OK;
}
static zupt_error_t read_enc_header(FILE *f, zupt_archive_header_t *hdr, zupt_options_t *opts) {
zupt_error_t read_enc_header(FILE *f, zupt_archive_header_t *hdr, zupt_options_t *opts) {
if (!(hdr->global_flags & ZUPT_FLAG_ENCRYPTED)) return ZUPT_OK;
fseeko(f, (int64_t)hdr->encryption_header_off, SEEK_SET);