Skip to content

Commit 35a8445

Browse files
committed
Avoid flipping translog header version (#58866)
An old translog header does not have a checksum. If we flip the header version of an empty translog to the older version, then we won't detect that corruption, and translog will be considered clean as before. Closes #58671
1 parent 5c71f63 commit 35a8445

File tree

3 files changed

+80
-29
lines changed

3 files changed

+80
-29
lines changed

server/src/main/java/org/elasticsearch/index/translog/TranslogHeader.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.elasticsearch.common.io.Channels;
3030
import org.elasticsearch.common.io.stream.InputStreamStreamInput;
3131
import org.elasticsearch.common.io.stream.OutputStreamStreamOutput;
32+
import org.elasticsearch.common.io.stream.StreamInput;
3233

3334
import java.io.EOFException;
3435
import java.io.IOException;
@@ -105,6 +106,20 @@ private static int headerSizeInBytes(int version, int uuidLength) {
105106
return size;
106107
}
107108

109+
static int readHeaderVersion(final Path path, final FileChannel channel, final StreamInput in) throws IOException {
110+
final int version;
111+
try {
112+
version = CodecUtil.checkHeader(new InputStreamDataInput(in), TRANSLOG_CODEC, VERSION_CHECKSUMS, VERSION_PRIMARY_TERM);
113+
} catch (CorruptIndexException | IndexFormatTooOldException | IndexFormatTooNewException e) {
114+
tryReportOldVersionError(path, channel);
115+
throw new TranslogCorruptedException(path.toString(), "translog header corrupted", e);
116+
}
117+
if (version == VERSION_CHECKSUMS) {
118+
throw new IllegalStateException("pre-2.0 translog found [" + path + "]");
119+
}
120+
return version;
121+
}
122+
108123
/**
109124
* Read a translog header from the given path and file channel
110125
*/
@@ -115,16 +130,7 @@ static TranslogHeader read(final String translogUUID, final Path path, final Fil
115130
new BufferedChecksumStreamInput(
116131
new InputStreamStreamInput(java.nio.channels.Channels.newInputStream(channel), channel.size()),
117132
path.toString());
118-
final int version;
119-
try {
120-
version = CodecUtil.checkHeader(new InputStreamDataInput(in), TRANSLOG_CODEC, VERSION_CHECKSUMS, VERSION_PRIMARY_TERM);
121-
} catch (CorruptIndexException | IndexFormatTooOldException | IndexFormatTooNewException e) {
122-
tryReportOldVersionError(path, channel);
123-
throw new TranslogCorruptedException(path.toString(), "translog header corrupted", e);
124-
}
125-
if (version == VERSION_CHECKSUMS) {
126-
throw new IllegalStateException("pre-2.0 translog found [" + path + "]");
127-
}
133+
final int version = readHeaderVersion(path, channel, in);
128134
// Read the translogUUID
129135
final int uuidLen = in.readInt();
130136
if (uuidLen > channel.size()) {

server/src/test/java/org/elasticsearch/index/translog/TestTranslog.java

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
import org.apache.lucene.index.IndexCommit;
2727
import org.apache.lucene.store.NIOFSDirectory;
2828
import org.apache.lucene.util.LuceneTestCase;
29+
import org.elasticsearch.common.io.stream.InputStreamStreamInput;
2930
import org.elasticsearch.core.internal.io.IOUtils;
3031
import org.elasticsearch.index.engine.CombinedDeletionPolicy;
3132

3233
import java.io.IOException;
3334
import java.nio.ByteBuffer;
35+
import java.nio.channels.Channels;
3436
import java.nio.channels.FileChannel;
3537
import java.nio.file.DirectoryStream;
3638
import java.nio.file.Files;
@@ -47,6 +49,7 @@
4749
import java.util.regex.Pattern;
4850

4951
import static org.elasticsearch.index.translog.Translog.CHECKPOINT_FILE_NAME;
52+
import static org.elasticsearch.index.translog.Translog.TRANSLOG_FILE_SUFFIX;
5053
import static org.hamcrest.MatcherAssert.assertThat;
5154
import static org.hamcrest.Matchers.empty;
5255
import static org.hamcrest.Matchers.equalTo;
@@ -151,26 +154,29 @@ static void corruptFile(Logger logger, Random random, Path fileToCorrupt, boolea
151154
final long corruptPosition = RandomNumbers.randomLongBetween(random, 0, fileSize - 1);
152155

153156
if (random.nextBoolean()) {
154-
// read
155-
fileChannel.position(corruptPosition);
156-
assertThat(fileChannel.position(), equalTo(corruptPosition));
157-
ByteBuffer bb = ByteBuffer.wrap(new byte[1]);
158-
fileChannel.read(bb);
159-
bb.flip();
160-
161-
// corrupt
162-
byte oldValue = bb.get(0);
163-
byte newValue;
164157
do {
165-
newValue = (byte) random.nextInt(0x100);
166-
} while (newValue == oldValue);
167-
bb.put(0, newValue);
168-
169-
// rewrite
170-
fileChannel.position(corruptPosition);
171-
fileChannel.write(bb);
172-
logger.info("corruptFile: corrupting file {} at position {} turning 0x{} into 0x{}", fileToCorrupt, corruptPosition,
173-
Integer.toHexString(oldValue & 0xff), Integer.toHexString(newValue & 0xff));
158+
// read
159+
fileChannel.position(corruptPosition);
160+
assertThat(fileChannel.position(), equalTo(corruptPosition));
161+
ByteBuffer bb = ByteBuffer.wrap(new byte[1]);
162+
fileChannel.read(bb);
163+
bb.flip();
164+
165+
// corrupt
166+
byte oldValue = bb.get(0);
167+
byte newValue;
168+
do {
169+
newValue = (byte) random.nextInt(0x100);
170+
} while (newValue == oldValue);
171+
bb.put(0, newValue);
172+
173+
// rewrite
174+
fileChannel.position(corruptPosition);
175+
fileChannel.write(bb);
176+
logger.info("corruptFile: corrupting file {} at position {} turning 0x{} into 0x{}", fileToCorrupt, corruptPosition,
177+
Integer.toHexString(oldValue & 0xff), Integer.toHexString(newValue & 0xff));
178+
} while (isTranslogHeaderVersionFlipped(fileToCorrupt, fileChannel));
179+
174180
} else {
175181
logger.info("corruptFile: truncating file {} from length {} to length {}", fileToCorrupt, fileSize, corruptPosition);
176182
fileChannel.truncate(corruptPosition);
@@ -233,4 +239,22 @@ public void close() {
233239
}
234240
};
235241
}
242+
243+
/**
244+
* An old translog header does not have a checksum. If we flip the header version of an empty translog from 3 to 2,
245+
* then we won't detect that corruption, and the translog will be considered clean as before.
246+
*/
247+
static boolean isTranslogHeaderVersionFlipped(Path corruptedFile, FileChannel channel) throws IOException {
248+
if (corruptedFile.toString().endsWith(TRANSLOG_FILE_SUFFIX) == false) {
249+
return false;
250+
}
251+
channel.position(0);
252+
final InputStreamStreamInput in = new InputStreamStreamInput(Channels.newInputStream(channel), channel.size());
253+
try {
254+
final int version = TranslogHeader.readHeaderVersion(corruptedFile, channel, in);
255+
return version == TranslogHeader.VERSION_CHECKPOINTS;
256+
} catch (IllegalStateException | TranslogCorruptedException | IOException e) {
257+
return false;
258+
}
259+
}
236260
}

server/src/test/java/org/elasticsearch/index/translog/TranslogHeaderTests.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636

3737
import static org.hamcrest.Matchers.anyOf;
3838
import static org.hamcrest.Matchers.containsString;
39+
import static org.hamcrest.Matchers.either;
3940
import static org.hamcrest.Matchers.equalTo;
41+
import static org.hamcrest.Matchers.instanceOf;
4042
import static org.hamcrest.Matchers.lessThan;
4143

4244
public class TranslogHeaderTests extends ESTestCase {
@@ -125,6 +127,25 @@ public void testLegacyTranslogVersions() throws Exception {
125127
IllegalStateException.class, "pre-2.0 translog");
126128
}
127129

130+
public void testCorruptTranslogHeader() throws Exception {
131+
final String translogUUID = UUIDs.randomBase64UUID();
132+
final TranslogHeader outHeader = new TranslogHeader(translogUUID, randomNonNegativeLong());
133+
final long generation = randomNonNegativeLong();
134+
final Path translogLocation = createTempDir();
135+
final Path translogFile = translogLocation.resolve(Translog.getFilename(generation));
136+
try (FileChannel channel = FileChannel.open(translogFile, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
137+
outHeader.write(channel);
138+
assertThat(outHeader.sizeInBytes(), equalTo((int) channel.position()));
139+
}
140+
TestTranslog.corruptFile(logger, random(), translogFile, false);
141+
final Exception error = expectThrows(Exception.class, () -> {
142+
try (FileChannel channel = FileChannel.open(translogFile, StandardOpenOption.READ)) {
143+
TranslogHeader.read(randomValueOtherThan(translogUUID, UUIDs::randomBase64UUID), translogFile, channel);
144+
}
145+
});
146+
assertThat(error, either(instanceOf(IllegalStateException.class)).or(instanceOf(TranslogCorruptedException.class)));
147+
}
148+
128149
private <E extends Exception> void checkFailsToOpen(String file, Class<E> expectedErrorType, String expectedMessage) {
129150
final Path translogFile = getDataPath(file);
130151
assertThat("test file [" + translogFile + "] should exist", Files.exists(translogFile), equalTo(true));

0 commit comments

Comments
 (0)