mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-26 01:23:13 +01:00
feat(savedata): add tier 1 data integrity protections
Prevent savedata corruption and denial-of-service by adding four layers of protection to the save pipeline: - Bounded decompression (nullcomp.DecompressWithLimit): caps output size to prevent OOM from crafted payloads that expand to exhaust memory - Bounds-checked delta patching (deltacomp.ApplyDataDiffWithLimit): validates offsets before writing, returns errors for negative offsets, truncated patches, and oversized output; ApplyDataDiff now returns original data on error instead of partial corruption - Size limits on save handlers: rejects compressed payloads >512KB and decompressed data >1MB before processing; applied to main savedata, platedata, and platebox diff paths - Rotating savedata backups: 3 slots per character with 30-minute interval, snapshots the previous state before overwriting, backed by new savedata_backups table (migration 0007)
This commit is contained in:
@@ -212,6 +212,32 @@ func (r *CharacterRepository) UpdateGCPAndPact(charID uint32, gcp uint32, pactID
|
||||
return err
|
||||
}
|
||||
|
||||
// SaveBackup upserts a savedata snapshot into the rotating backup table.
|
||||
func (r *CharacterRepository) SaveBackup(charID uint32, slot int, data []byte) error {
|
||||
_, err := r.db.Exec(`
|
||||
INSERT INTO savedata_backups (char_id, slot, savedata, saved_at)
|
||||
VALUES ($1, $2, $3, now())
|
||||
ON CONFLICT (char_id, slot) DO UPDATE SET savedata = $3, saved_at = now()
|
||||
`, charID, slot, data)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetLastBackupTime returns the most recent backup timestamp for a character.
|
||||
// Returns the zero time if no backups exist.
|
||||
func (r *CharacterRepository) GetLastBackupTime(charID uint32) (time.Time, error) {
|
||||
var t sql.NullTime
|
||||
err := r.db.QueryRow(
|
||||
"SELECT MAX(saved_at) FROM savedata_backups WHERE char_id = $1", charID,
|
||||
).Scan(&t)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
if !t.Valid {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
return t.Time, nil
|
||||
}
|
||||
|
||||
// FindByRastaID looks up name and id by rasta_id.
|
||||
func (r *CharacterRepository) FindByRastaID(rastaID int) (charID uint32, name string, err error) {
|
||||
err = r.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id=$1", rastaID).Scan(&name, &charID)
|
||||
|
||||
Reference in New Issue
Block a user