Merge pull request #13848 from s1monw/verify_written_checksum
Verify actually written checksum in VerifyingIndexOutput
This commit is contained in:
commit
f526754d0e
|
@ -39,6 +39,7 @@ import org.elasticsearch.common.io.stream.Writeable;
|
||||||
import org.elasticsearch.common.logging.ESLogger;
|
import org.elasticsearch.common.logging.ESLogger;
|
||||||
import org.elasticsearch.common.logging.Loggers;
|
import org.elasticsearch.common.logging.Loggers;
|
||||||
import org.elasticsearch.common.lucene.Lucene;
|
import org.elasticsearch.common.lucene.Lucene;
|
||||||
|
import org.elasticsearch.common.lucene.store.ByteArrayIndexInput;
|
||||||
import org.elasticsearch.common.lucene.store.InputStreamIndexInput;
|
import org.elasticsearch.common.lucene.store.InputStreamIndexInput;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
|
@ -1259,27 +1260,42 @@ public class Store extends AbstractIndexShardComponent implements Closeable, Ref
|
||||||
private long writtenBytes;
|
private long writtenBytes;
|
||||||
private final long checksumPosition;
|
private final long checksumPosition;
|
||||||
private String actualChecksum;
|
private String actualChecksum;
|
||||||
|
private final byte[] footerChecksum = new byte[8]; // this holds the actual footer checksum data written by to this output
|
||||||
|
|
||||||
LuceneVerifyingIndexOutput(StoreFileMetaData metadata, IndexOutput out) {
|
LuceneVerifyingIndexOutput(StoreFileMetaData metadata, IndexOutput out) {
|
||||||
super(out);
|
super(out);
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
checksumPosition = metadata.length() - 8; // the last 8 bytes are the checksum
|
checksumPosition = metadata.length() - 8; // the last 8 bytes are the checksum - we store it in footerChecksum
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void verify() throws IOException {
|
public void verify() throws IOException {
|
||||||
|
String footerDigest = null;
|
||||||
if (metadata.checksum().equals(actualChecksum) && writtenBytes == metadata.length()) {
|
if (metadata.checksum().equals(actualChecksum) && writtenBytes == metadata.length()) {
|
||||||
|
ByteArrayIndexInput indexInput = new ByteArrayIndexInput("checksum", this.footerChecksum);
|
||||||
|
footerDigest = digestToString(indexInput.readLong());
|
||||||
|
if (metadata.checksum().equals(footerDigest)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
throw new CorruptIndexException("verification failed (hardware problem?) : expected=" + metadata.checksum() +
|
throw new CorruptIndexException("verification failed (hardware problem?) : expected=" + metadata.checksum() +
|
||||||
" actual=" + actualChecksum + " writtenLength=" + writtenBytes + " expectedLength=" + metadata.length() +
|
" actual=" + actualChecksum + " footer=" + footerDigest +" writtenLength=" + writtenBytes + " expectedLength=" + metadata.length() +
|
||||||
" (resource=" + metadata.toString() + ")", "VerifyingIndexOutput(" + metadata.name() + ")");
|
" (resource=" + metadata.toString() + ")", "VerifyingIndexOutput(" + metadata.name() + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeByte(byte b) throws IOException {
|
public void writeByte(byte b) throws IOException {
|
||||||
if (writtenBytes++ == checksumPosition) {
|
final long writtenBytes = this.writtenBytes++;
|
||||||
|
if (writtenBytes == checksumPosition) {
|
||||||
readAndCompareChecksum();
|
readAndCompareChecksum();
|
||||||
|
} else if (writtenBytes > checksumPosition) { // we are writing parts of the checksum....
|
||||||
|
final int index = Math.toIntExact(writtenBytes - checksumPosition);
|
||||||
|
if (index < footerChecksum.length) {
|
||||||
|
footerChecksum[index] = b;
|
||||||
|
} else {
|
||||||
|
verify(); // fail if we write more than expected
|
||||||
|
throw new AssertionError("write past EOF expected length: " + metadata.length() + " writtenBytes: " + writtenBytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
out.writeByte(b);
|
out.writeByte(b);
|
||||||
}
|
}
|
||||||
|
@ -1295,7 +1311,8 @@ public class Store extends AbstractIndexShardComponent implements Closeable, Ref
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeBytes(byte[] b, int offset, int length) throws IOException {
|
public void writeBytes(byte[] b, int offset, int length) throws IOException {
|
||||||
if (writtenBytes + length > checksumPosition && actualChecksum == null) {
|
if (writtenBytes + length > checksumPosition) {
|
||||||
|
if (actualChecksum == null) {
|
||||||
assert writtenBytes <= checksumPosition;
|
assert writtenBytes <= checksumPosition;
|
||||||
final int bytesToWrite = (int) (checksumPosition - writtenBytes);
|
final int bytesToWrite = (int) (checksumPosition - writtenBytes);
|
||||||
out.writeBytes(b, offset, bytesToWrite);
|
out.writeBytes(b, offset, bytesToWrite);
|
||||||
|
@ -1304,9 +1321,14 @@ public class Store extends AbstractIndexShardComponent implements Closeable, Ref
|
||||||
length -= bytesToWrite;
|
length -= bytesToWrite;
|
||||||
writtenBytes += bytesToWrite;
|
writtenBytes += bytesToWrite;
|
||||||
}
|
}
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
writeByte(b[offset+i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
out.writeBytes(b, offset, length);
|
out.writeBytes(b, offset, length);
|
||||||
writtenBytes += length;
|
writtenBytes += length;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,16 +149,85 @@ public class StoreTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Store.verify(verifyingOutput);
|
Store.verify(verifyingOutput);
|
||||||
verifyingOutput.writeByte((byte) 0x0);
|
try {
|
||||||
|
appendRandomData(verifyingOutput);
|
||||||
|
fail("should be a corrupted index");
|
||||||
|
} catch (CorruptIndexException | IndexFormatTooOldException | IndexFormatTooNewException ex) {
|
||||||
|
// ok
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Store.verify(verifyingOutput);
|
Store.verify(verifyingOutput);
|
||||||
fail("should be a corrupted index");
|
fail("should be a corrupted index");
|
||||||
} catch (CorruptIndexException | IndexFormatTooOldException | IndexFormatTooNewException ex) {
|
} catch (CorruptIndexException | IndexFormatTooOldException | IndexFormatTooNewException ex) {
|
||||||
// ok
|
// ok
|
||||||
}
|
}
|
||||||
|
|
||||||
IOUtils.close(indexInput, verifyingOutput, dir);
|
IOUtils.close(indexInput, verifyingOutput, dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testChecksumCorrupted() throws IOException {
|
||||||
|
Directory dir = newDirectory();
|
||||||
|
IndexOutput output = dir.createOutput("foo.bar", IOContext.DEFAULT);
|
||||||
|
int iters = scaledRandomIntBetween(10, 100);
|
||||||
|
for (int i = 0; i < iters; i++) {
|
||||||
|
BytesRef bytesRef = new BytesRef(TestUtil.randomRealisticUnicodeString(random(), 10, 1024));
|
||||||
|
output.writeBytes(bytesRef.bytes, bytesRef.offset, bytesRef.length);
|
||||||
|
}
|
||||||
|
output.writeInt(CodecUtil.FOOTER_MAGIC);
|
||||||
|
output.writeInt(0);
|
||||||
|
String checksum = Store.digestToString(output.getChecksum());
|
||||||
|
output.writeLong(output.getChecksum() + 1); // write a wrong checksum to the file
|
||||||
|
output.close();
|
||||||
|
|
||||||
|
IndexInput indexInput = dir.openInput("foo.bar", IOContext.DEFAULT);
|
||||||
|
indexInput.seek(0);
|
||||||
|
BytesRef ref = new BytesRef(scaledRandomIntBetween(1, 1024));
|
||||||
|
long length = indexInput.length();
|
||||||
|
IndexOutput verifyingOutput = new Store.LuceneVerifyingIndexOutput(new StoreFileMetaData("foo1.bar", length, checksum), dir.createOutput("foo1.bar", IOContext.DEFAULT));
|
||||||
|
while (length > 0) {
|
||||||
|
if (random().nextInt(10) == 0) {
|
||||||
|
verifyingOutput.writeByte(indexInput.readByte());
|
||||||
|
length--;
|
||||||
|
} else {
|
||||||
|
int min = (int) Math.min(length, ref.bytes.length);
|
||||||
|
indexInput.readBytes(ref.bytes, ref.offset, min);
|
||||||
|
verifyingOutput.writeBytes(ref.bytes, ref.offset, min);
|
||||||
|
length -= min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (randomBoolean()) {
|
||||||
|
appendRandomData(verifyingOutput);
|
||||||
|
} else {
|
||||||
|
Store.verify(verifyingOutput);
|
||||||
|
}
|
||||||
|
fail("should be a corrupted index");
|
||||||
|
} catch (CorruptIndexException | IndexFormatTooOldException | IndexFormatTooNewException ex) {
|
||||||
|
// ok
|
||||||
|
}
|
||||||
|
IOUtils.close(indexInput, verifyingOutput, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendRandomData(IndexOutput output) throws IOException {
|
||||||
|
int numBytes = randomIntBetween(1, 1024);
|
||||||
|
final BytesRef ref = new BytesRef(scaledRandomIntBetween(1, numBytes));
|
||||||
|
ref.length = ref.bytes.length;
|
||||||
|
while (numBytes > 0) {
|
||||||
|
if (random().nextInt(10) == 0) {
|
||||||
|
output.writeByte(randomByte());
|
||||||
|
numBytes--;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i<ref.length; i++) {
|
||||||
|
ref.bytes[i] = randomByte();
|
||||||
|
}
|
||||||
|
final int min = Math.min(numBytes, ref.bytes.length);
|
||||||
|
output.writeBytes(ref.bytes, ref.offset, min);
|
||||||
|
numBytes -= min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testVerifyingIndexOutputWithBogusInput() throws IOException {
|
public void testVerifyingIndexOutputWithBogusInput() throws IOException {
|
||||||
Directory dir = newDirectory();
|
Directory dir = newDirectory();
|
||||||
|
|
Loading…
Reference in New Issue