mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-14 01:03:28 +01:00
Prevent stack overflow when walking message revision chains during local backup.
This commit is contained in:
committed by
Greyson Parrelli
parent
a4793ebba5
commit
603e12dd59
@@ -62,6 +62,7 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -597,23 +598,44 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
}
|
||||
|
||||
private static boolean isNonExpiringMessage(@NonNull SQLiteDatabase db, @NonNull Cursor cursor) {
|
||||
long id = CursorUtil.requireLong(cursor, MessageTable.ID);
|
||||
long expireStarted = CursorUtil.requireLong(cursor, MessageTable.EXPIRE_STARTED);
|
||||
long expiresIn = CursorUtil.requireLong(cursor, MessageTable.EXPIRES_IN);
|
||||
long latestRevisionId = CursorUtil.requireLong(cursor, MessageTable.LATEST_REVISION_ID);
|
||||
return isNonExpiringMessage(db,
|
||||
CursorUtil.requireLong(cursor, MessageTable.ID),
|
||||
CursorUtil.requireLong(cursor, MessageTable.EXPIRE_STARTED),
|
||||
CursorUtil.requireLong(cursor, MessageTable.EXPIRES_IN),
|
||||
CursorUtil.requireLong(cursor, MessageTable.LATEST_REVISION_ID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Corrupt data can contain a cycle in {@link MessageTable#LATEST_REVISION_ID}, so the walk tracks visited ids.
|
||||
*/
|
||||
private static boolean isNonExpiringMessage(@NonNull SQLiteDatabase db, long id, long expireStarted, long expiresIn, long latestRevisionId) {
|
||||
String[] columns = new String[] { MessageTable.ID, MessageTable.EXPIRE_STARTED, MessageTable.EXPIRES_IN, MessageTable.LATEST_REVISION_ID };
|
||||
String where = MessageTable.ID + " = ?";
|
||||
|
||||
Set<Long> visited = new HashSet<>();
|
||||
|
||||
while (latestRevisionId > 0 && latestRevisionId != id) {
|
||||
if (!visited.add(id)) {
|
||||
Log.w(TAG, "Detected a cycle in the message revision chain! Stopping the walk at " + id);
|
||||
break;
|
||||
}
|
||||
|
||||
try (Cursor cursor = db.query(MessageTable.TABLE_NAME, columns, where, SqlUtil.buildArgs(latestRevisionId), null, null, null)) {
|
||||
if (cursor == null || !cursor.moveToFirst()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
id = CursorUtil.requireLong(cursor, MessageTable.ID);
|
||||
expireStarted = CursorUtil.requireLong(cursor, MessageTable.EXPIRE_STARTED);
|
||||
expiresIn = CursorUtil.requireLong(cursor, MessageTable.EXPIRES_IN);
|
||||
latestRevisionId = CursorUtil.requireLong(cursor, MessageTable.LATEST_REVISION_ID);
|
||||
}
|
||||
}
|
||||
|
||||
long expiresAt = expireStarted + expiresIn;
|
||||
long timeRemaining = expiresAt - System.currentTimeMillis();
|
||||
|
||||
if (latestRevisionId > 0 && latestRevisionId != id ) {
|
||||
return isForNonExpiringMessage(db, latestRevisionId);
|
||||
}
|
||||
|
||||
if (expireStarted > 0 && timeRemaining <= EXPIRATION_BACKUP_THRESHOLD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return !(expireStarted > 0 && timeRemaining <= EXPIRATION_BACKUP_THRESHOLD);
|
||||
}
|
||||
|
||||
private static boolean isForNonExpiringMessage(@NonNull SQLiteDatabase db, long messageId) {
|
||||
|
||||
Reference in New Issue
Block a user