When merging two states, validate that the transaction signature we are using for the "before" layer matches the "previous transaction signature" that is baked into the "after" layer.

This defends against a missing or out-of-order transaction. If this ever fails validation, we may need to rethink the way we are requesting transactions. But in theory this shouldn't happen, given that the "last reference" field of a transaction ensures that out-of-order transactions are invalid already.
This commit is contained in:
CalDescent 2021-08-15 20:27:16 +01:00
parent 219a5db60c
commit 94da1a30dc
2 changed files with 25 additions and 2 deletions

View File

@ -154,7 +154,8 @@ public class ArbitraryDataBuilder {
for (int i=1; i<paths.size(); i++) { for (int i=1; i<paths.size(); i++) {
LOGGER.info(String.format("[%s][%s] Applying layer %d...", this.service, this.name, i)); LOGGER.info(String.format("[%s][%s] Applying layer %d...", this.service, this.name, i));
Path pathAfter = this.paths.get(i); Path pathAfter = this.paths.get(i);
ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(pathBefore, pathAfter); byte[] signatureBefore = this.transactions.get(i-1).getSignature();
ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(pathBefore, pathAfter, signatureBefore);
combiner.combine(); combiner.combine();
combiner.cleanup(); combiner.cleanup();
pathBefore = combiner.getFinalPath(); pathBefore = combiner.getFinalPath();

View File

@ -10,6 +10,7 @@ import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException; import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays;
public class ArbitraryDataCombiner { public class ArbitraryDataCombiner {
@ -17,16 +18,19 @@ public class ArbitraryDataCombiner {
private Path pathBefore; private Path pathBefore;
private Path pathAfter; private Path pathAfter;
private byte[] signatureBefore;
private Path finalPath; private Path finalPath;
public ArbitraryDataCombiner(Path pathBefore, Path pathAfter) { public ArbitraryDataCombiner(Path pathBefore, Path pathAfter, byte[] signatureBefore) {
this.pathBefore = pathBefore; this.pathBefore = pathBefore;
this.pathAfter = pathAfter; this.pathAfter = pathAfter;
this.signatureBefore = signatureBefore;
} }
public void combine() throws IOException { public void combine() throws IOException {
try { try {
this.preExecute(); this.preExecute();
this.validatePreviousSignature();
this.process(); this.process();
} finally { } finally {
@ -78,6 +82,24 @@ public class ArbitraryDataCombiner {
} }
private void validatePreviousSignature() throws IOException {
if (this.signatureBefore == null) {
throw new IllegalStateException("No previous signature passed to the combiner");
}
ArbitraryDataMetadata metadata = new ArbitraryDataMetadata(this.pathAfter);
metadata.read();
byte[] previousSignature = metadata.getPreviousSignature();
if (previousSignature == null) {
throw new IllegalStateException("Unable to extract previous signature from patch metadata");
}
// Compare the signatures
if (!Arrays.equals(previousSignature, this.signatureBefore)) {
throw new IllegalStateException("Previous signatures do not match - transactions out of order?");
}
}
private void process() throws IOException { private void process() throws IOException {
ArbitraryDataMerge merge = new ArbitraryDataMerge(this.pathBefore, this.pathAfter); ArbitraryDataMerge merge = new ArbitraryDataMerge(this.pathBefore, this.pathAfter);
merge.compute(); merge.compute();