Skip to content

Commit 00bdb63

Browse files
Upgrade lz4 dependency to 1.10.1 (elastic#139221)
Upgrading lz4 to the latest 1.10.1 version.
1 parent b75918f commit 00bdb63

File tree

7 files changed

+365
-77
lines changed

7 files changed

+365
-77
lines changed

docs/changelog/139221.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 139221
2+
summary: Upgrade lz4 dependency to 1.10.1
3+
area: Infra/Core
4+
type: upgrade
5+
issues: []

gradle/verification-metadata.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@
6262
<sha256 value="333ff5369043975b7e031b8b27206937441854738e038c1f47f98d072a20437a" origin="Generated by Gradle"/>
6363
</artifact>
6464
</component>
65-
<component group="at.yawk.lz4" name="lz4-java" version="1.8.1">
66-
<artifact name="lz4-java-1.8.1.jar">
67-
<sha256 value="1de8ff91b90fba9d4896b8617ed3594c2e4390d321d5a92c34f80e62e406c1b0" origin="Generated by Gradle"/>
65+
<component group="at.yawk.lz4" name="lz4-java" version="1.10.1">
66+
<artifact name="lz4-java-1.10.1.jar">
67+
<sha256 value="a58a84c4271e50df4c96ed916ccb7e48a869f8ed9cdcda1ad5d3d4c33b0214a3" origin="Generated by Gradle"/>
6868
</artifact>
6969
</component>
7070
<component group="cglib" name="cglib-nodep" version="3.3.0">

libs/lz4/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
apply plugin: 'elasticsearch.publish'
1010

1111
dependencies {
12-
api 'at.yawk.lz4:lz4-java:1.8.1'
12+
api 'at.yawk.lz4:lz4-java:1.10.1'
1313
api project(':libs:core')
1414

1515
testImplementation(project(":test:framework")) {

libs/lz4/src/main/java/org/elasticsearch/lz4/ESLZ4Decompressor.java

Lines changed: 132 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -20,98 +20,169 @@
2020

2121
import net.jpountz.lz4.LZ4Exception;
2222
import net.jpountz.lz4.LZ4FastDecompressor;
23+
import net.jpountz.util.ByteBufferUtils;
2324

2425
import java.nio.ByteBuffer;
2526

27+
import static org.elasticsearch.lz4.LZ4Constants.COPY_LENGTH;
28+
import static org.elasticsearch.lz4.LZ4Constants.ML_BITS;
29+
import static org.elasticsearch.lz4.LZ4Constants.RUN_MASK;
30+
import static org.elasticsearch.lz4.LZ4Utils.notEnoughSpace;
31+
2632
/**
27-
* This file is forked from https://github.com/lz4/lz4-java. In particular, it forks the following file
28-
* net.jpountz.lz4.LZ4JavaSafeFastDecompressor.
29-
*
30-
* It modifies the original implementation to use custom LZ4SafeUtils and SafeUtils implementations which
31-
* include performance improvements.
33+
* This file is a vendored version of {@code net.jpountz.lz4.LZ4JavaSafeFastDecompressor} from
34+
* <a href="https://github.com/yawkat/lz4-java">yawkat/lz4-java</a>. To obtain the original file, check out the {@code lz4-java} repository
35+
* and run {@code mvn clean install} which will generate the original source of this class at
36+
* {@code target/generated-sources/mvel/net/jpountz/lz4/LZ4JavaSafeFastDecompressor.java}.
37+
* <p>
38+
* It modifies the original implementation to use local {@link LZ4SafeUtils} and {@link SafeUtils} implementations which include some
39+
* performance optimisations, and it also drops support for decompressing data from a direct (non-heap) {@link ByteBuffer}.
40+
* <p>
41+
* Differences from the original are annotated with [ES change from upstream]
3242
*/
3343
public class ESLZ4Decompressor extends LZ4FastDecompressor {
3444
public static final LZ4FastDecompressor INSTANCE = new ESLZ4Decompressor();
3545

36-
ESLZ4Decompressor() {}
46+
private ESLZ4Decompressor() {}
47+
48+
@Override
49+
public int decompress(byte[] src, final int srcOff, byte[] dest, final int destOff, int destLen) {
50+
51+
final int srcEnd = src.length;
52+
53+
return decompress(src, srcOff, srcEnd - srcOff, dest, destOff, destLen);
54+
}
3755

38-
public int decompress(byte[] src, int srcOff, byte[] dest, int destOff, int destLen) {
39-
SafeUtils.checkRange(src, srcOff);
56+
private int decompress(byte[] src, final int srcOff, final int srcLen, byte[] dest, final int destOff, int destLen) {
57+
SafeUtils.checkRange(src, srcOff, srcLen);
4058
SafeUtils.checkRange(dest, destOff, destLen);
59+
4160
if (destLen == 0) {
42-
if (SafeUtils.readByte(src, srcOff) != 0) {
61+
// Allow `srcLen > 1` despite just one byte being consumed since this 'fast' decompressor does not have to fully consume the src
62+
if (srcLen < 1 || SafeUtils.readByte(src, srcOff) != 0) {
4363
throw new LZ4Exception("Malformed input at " + srcOff);
44-
} else {
45-
return 1;
4664
}
47-
} else {
48-
int destEnd = destOff + destLen;
49-
int sOff = srcOff;
50-
int dOff = destOff;
51-
52-
while (true) {
53-
int token = SafeUtils.readByte(src, sOff) & 255;
54-
++sOff;
55-
int literalLen = token >>> 4;
56-
if (literalLen == 15) {
57-
byte len;
58-
for (boolean var11 = true; (len = SafeUtils.readByte(src, sOff++)) == -1; literalLen += 255) {
59-
}
65+
return 1;
66+
}
6067

61-
literalLen += len & 255;
62-
}
68+
final int srcEnd = srcOff + srcLen;
69+
final int destEnd = destOff + destLen;
6370

64-
int literalCopyEnd = dOff + literalLen;
65-
if (literalCopyEnd > destEnd - 8) {
66-
if (literalCopyEnd != destEnd) {
67-
throw new LZ4Exception("Malformed input at " + sOff);
68-
} else {
69-
LZ4SafeUtils.safeArraycopy(src, sOff, dest, dOff, literalLen);
70-
sOff += literalLen;
71-
return sOff - srcOff;
71+
int sOff = srcOff;
72+
int dOff = destOff;
73+
74+
while (true) {
75+
if (sOff >= srcEnd) {
76+
throw new LZ4Exception("Malformed input at " + sOff);
77+
}
78+
final int token = SafeUtils.readByte(src, sOff) & 0xFF;
79+
++sOff;
80+
81+
// literals
82+
int literalLen = token >>> ML_BITS;
83+
if (literalLen == RUN_MASK) {
84+
byte len = (byte) 0xFF;
85+
while (sOff < srcEnd && (len = SafeUtils.readByte(src, sOff++)) == (byte) 0xFF) {
86+
literalLen += 0xFF;
87+
if (literalLen < 0) {
88+
throw new LZ4Exception("Too large literalLen");
7289
}
7390
}
91+
literalLen += len & 0xFF;
92+
}
93+
94+
final int literalCopyEnd = dOff + literalLen;
95+
// Check for overflow
96+
if (literalCopyEnd < dOff) {
97+
throw new LZ4Exception("Too large literalLen");
98+
}
7499

75-
LZ4SafeUtils.wildArraycopy(src, sOff, dest, dOff, literalLen);
76-
sOff += literalLen;
77-
int matchDec = SafeUtils.readShortLE(src, sOff);
78-
sOff += 2;
79-
int matchOff = literalCopyEnd - matchDec;
80-
if (matchOff < destOff) {
100+
if (notEnoughSpace(destEnd - literalCopyEnd, COPY_LENGTH) || notEnoughSpace(srcEnd - sOff, COPY_LENGTH + literalLen)) {
101+
102+
if (literalCopyEnd != destEnd) {
103+
throw new LZ4Exception("Malformed input at " + sOff);
104+
} else if (notEnoughSpace(srcEnd - sOff, literalLen)) {
81105
throw new LZ4Exception("Malformed input at " + sOff);
106+
107+
} else {
108+
LZ4SafeUtils.safeArraycopy(src, sOff, dest, dOff, literalLen);
109+
sOff += literalLen;
110+
dOff = literalCopyEnd;
111+
break; // EOF
82112
}
113+
}
83114

84-
int matchLen = token & 15;
85-
if (matchLen == 15) {
86-
byte len;
87-
for (boolean var15 = true; (len = SafeUtils.readByte(src, sOff++)) == -1; matchLen += 255) {
88-
}
115+
LZ4SafeUtils.wildArraycopy(src, sOff, dest, dOff, literalLen);
116+
sOff += literalLen;
117+
dOff = literalCopyEnd;
89118

90-
matchLen += len & 255;
91-
}
119+
// matchs
120+
final int matchDec = SafeUtils.readShortLE(src, sOff);
121+
sOff += 2;
122+
int matchOff = dOff - matchDec;
92123

93-
matchLen += 4;
94-
int matchCopyEnd = literalCopyEnd + matchLen;
95-
if (matchCopyEnd > destEnd - 8) {
96-
if (matchCopyEnd > destEnd) {
97-
throw new LZ4Exception("Malformed input at " + sOff);
98-
}
124+
if (matchOff < destOff) {
125+
throw new LZ4Exception("Malformed input at " + sOff);
126+
}
99127

100-
LZ4SafeUtils.safeIncrementalCopy(dest, matchOff, literalCopyEnd, matchLen);
101-
} else {
102-
LZ4SafeUtils.wildIncrementalCopy(dest, matchOff, literalCopyEnd, matchCopyEnd);
128+
int matchLen = token & LZ4Constants.ML_MASK;
129+
if (matchLen == LZ4Constants.ML_MASK) {
130+
byte len = (byte) 0xFF;
131+
while (sOff < srcEnd && (len = SafeUtils.readByte(src, sOff++)) == (byte) 0xFF) {
132+
matchLen += 0xFF;
133+
if (matchLen < 0) {
134+
throw new LZ4Exception("Too large matchLen");
135+
}
103136
}
137+
matchLen += len & 0xFF;
138+
}
139+
matchLen += LZ4Constants.MIN_MATCH;
140+
141+
final int matchCopyEnd = dOff + matchLen;
142+
// Check for overflow
143+
if (matchCopyEnd < dOff) {
144+
throw new LZ4Exception("Too large matchLen");
145+
}
104146

105-
dOff = matchCopyEnd;
147+
if (matchDec == 0) {
148+
if (matchCopyEnd > destEnd) {
149+
throw new LZ4Exception("Malformed input at " + sOff);
150+
}
151+
// With matchDec == 0, matchOff == dOff, so we'd copy in place. Zero the data instead. (CVE-2025-66566)
152+
assert matchOff == dOff; // should always hold, but this extra check will trigger during fuzzing if my logic is wrong
153+
LZ4Utils.zero(dest, dOff, matchCopyEnd);
154+
} else if (notEnoughSpace(destEnd - matchCopyEnd, COPY_LENGTH)) {
155+
if (matchCopyEnd > destEnd) {
156+
throw new LZ4Exception("Malformed input at " + sOff);
157+
}
158+
LZ4SafeUtils.safeIncrementalCopy(dest, matchOff, dOff, matchLen);
159+
} else {
160+
LZ4SafeUtils.wildIncrementalCopy(dest, matchOff, dOff, matchCopyEnd);
106161
}
162+
dOff = matchCopyEnd;
107163
}
164+
165+
return sOff - srcOff;
166+
108167
}
109168

110-
public int decompress(ByteBuffer src, int srcOff, ByteBuffer dest, int destOff, int destLen) {
169+
@Override
170+
public int decompress(ByteBuffer src, final int srcOff, ByteBuffer dest, final int destOff, int destLen) {
171+
172+
final int srcEnd = src.capacity();
173+
174+
return decompress(src, srcOff, srcEnd - srcOff, dest, destOff, destLen);
175+
}
176+
177+
private int decompress(ByteBuffer src, final int srcOff, final int srcLen, ByteBuffer dest, final int destOff, int destLen) {
178+
ByteBufferUtils.checkRange(src, srcOff, srcLen);
179+
ByteBufferUtils.checkRange(dest, destOff, destLen);
180+
111181
if (src.hasArray() && dest.hasArray()) {
112-
return this.decompress(src.array(), srcOff + src.arrayOffset(), dest.array(), destOff + dest.arrayOffset(), destLen);
113-
} else {
114-
throw new AssertionError("Do not support decompression on direct buffers");
182+
return decompress(src.array(), srcOff + src.arrayOffset(), srcLen, dest.array(), destOff + dest.arrayOffset(), destLen);
115183
}
184+
185+
// [ES change from upstream]: remove unused code
186+
throw new AssertionError("Do not support decompression on direct buffers");
116187
}
117188
}

libs/lz4/src/main/java/org/elasticsearch/lz4/LZ4SafeUtils.java

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,21 @@
3030
import static org.elasticsearch.lz4.LZ4Constants.ML_BITS;
3131
import static org.elasticsearch.lz4.LZ4Constants.ML_MASK;
3232
import static org.elasticsearch.lz4.LZ4Constants.RUN_MASK;
33+
import static org.elasticsearch.lz4.LZ4Utils.lengthOfEncodedInteger;
34+
import static org.elasticsearch.lz4.LZ4Utils.notEnoughSpace;
3335

3436
/**
35-
* This file is forked from https://github.com/lz4/lz4-java. In particular, it forks the following file
36-
* net.jpountz.lz4.LZ4SafeUtils.
37+
* This file is a vendored version of {@code net.jpountz.lz4.LZ4SafeUtils} from
38+
* <a href="https://github.com/yawkat/lz4-java">yawkat/lz4-java</a>.
39+
* <p>
40+
* Differences from the original are annotated with [ES change from upstream]
3741
*
38-
* It modifies the original implementation to use Java9 array mismatch method and varhandle performance
39-
* improvements. Comments are included to mark the changes.
42+
* @see <a href="https://github.com/yawkat/lz4-java/blob/v1.10.1/src/java/net/jpountz/lz4/LZ4SafeUtils.java">LZ4SafeUtils.java</a>
4043
*/
4144
enum LZ4SafeUtils {
4245
;
4346

44-
// Added VarHandle
47+
// [ES change from upstream]: define VarHandles allowing us to do operations without direct byte[] operations
4548
private static final VarHandle intPlatformNative = MethodHandles.byteArrayViewVarHandle(int[].class, Utils.NATIVE_BYTE_ORDER);
4649
private static final VarHandle longPlatformNative = MethodHandles.byteArrayViewVarHandle(long[].class, Utils.NATIVE_BYTE_ORDER);
4750

@@ -54,6 +57,7 @@ static int hash64k(byte[] buf, int i) {
5457
}
5558

5659
static boolean readIntEquals(byte[] buf, int i, int j) {
60+
// [ES change from upstream]: use SafeUtils.readInt instead of comparing byte-by-byte
5761
return SafeUtils.readInt(buf, i) == SafeUtils.readInt(buf, j);
5862
}
5963

@@ -63,16 +67,20 @@ static void safeIncrementalCopy(byte[] dest, int matchOff, int dOff, int matchLe
6367
}
6468
}
6569

66-
// Modified wildIncrementalCopy to mirror version in LZ4UnsafeUtils
6770
static void wildIncrementalCopy(byte[] dest, int matchOff, int dOff, int matchCopyEnd) {
71+
// [ES change from upstream]: use the implementation from LZ4UnsafeUtils#wildIncrementalCopy
72+
// see https://github.com/yawkat/lz4-java/blob/v1.10.1/src/java-unsafe/net/jpountz/lz4/LZ4UnsafeUtils.java#L55-L99
73+
// with further modifications as noted with [ES change from upstream] comments
6874
if (dOff - matchOff < 4) {
6975
for (int i = 0; i < 4; ++i) {
76+
// [ES change from upstream]: just copy the byte directly, don't use readByte and writeByte
7077
dest[dOff + i] = dest[matchOff + i];
7178
}
7279
dOff += 4;
7380
matchOff += 4;
7481
int dec = 0;
7582
assert dOff >= matchOff && dOff - matchOff < 8;
83+
// [ES change from upstream]: use enhanced switch
7684
switch (dOff - matchOff) {
7785
case 1 -> matchOff -= 3;
7886
case 2 -> matchOff -= 2;
@@ -85,32 +93,35 @@ static void wildIncrementalCopy(byte[] dest, int matchOff, int dOff, int matchCo
8593
case 7 -> dec = 3;
8694
}
8795

96+
// [ES change from upstream]: use copy4Bytes instead of readInt and writeInt
8897
copy4Bytes(dest, matchOff, dest, dOff);
8998
dOff += 4;
9099
matchOff -= dec;
91100
} else if (dOff - matchOff < LZ4Constants.COPY_LENGTH) {
101+
// [ES change from upstream]: use copy8Bytes instead of readLong and writeLong
92102
copy8Bytes(dest, matchOff, dest, dOff);
93103
dOff += dOff - matchOff;
94104
}
95105
while (dOff < matchCopyEnd) {
106+
// [ES change from upstream]: use copy8Bytes instead of readLong and writeLong
96107
copy8Bytes(dest, matchOff, dest, dOff);
97108
dOff += 8;
98109
matchOff += 8;
99110
}
100111
}
101112

102-
// Modified to use VarHandle
103113
static void copy8Bytes(byte[] src, int sOff, byte[] dest, int dOff) {
114+
// [ES change from upstream]: use VarHandles instead of a byte-by-byte loop
104115
longPlatformNative.set(dest, dOff, (long) longPlatformNative.get(src, sOff));
105116
}
106117

107-
// Added to copy single int
118+
// [ES change from upstream]: add this method to copy a single int (using VarHandles similarly to copy8Bytes)
108119
static void copy4Bytes(byte[] src, int sOff, byte[] dest, int dOff) {
109120
intPlatformNative.set(dest, dOff, (int) intPlatformNative.get(src, sOff));
110121
}
111122

112-
// Modified to use Arrays.mismatch
113123
static int commonBytes(byte[] b, int o1, int o2, int limit) {
124+
// [ES change from upstream]: use Arrays#mismatch instead of a byte-by-byte loop
114125
int mismatch = Arrays.mismatch(b, o1, limit, b, o2, limit);
115126
return mismatch == -1 ? limit : mismatch;
116127
}
@@ -132,8 +143,8 @@ static void wildArraycopy(byte[] src, int sOff, byte[] dest, int dOff, int len)
132143
for (int i = 0; i < len; i += 8) {
133144
copy8Bytes(src, sOff + i, dest, dOff + i);
134145
}
135-
// Modified to catch IndexOutOfBoundsException instead of ArrayIndexOutOfBoundsException.
136-
// VarHandles throw IndexOutOfBoundsException
146+
// [ES change from upstream]: Varhandles throw an IndexOutOfBoundsException instead of ArrayIndexOutOfBoundsException so we
147+
// change the type of exception caught here.
137148
} catch (IndexOutOfBoundsException e) {
138149
throw new LZ4Exception("Malformed input at offset " + sOff);
139150
}
@@ -184,7 +195,7 @@ static int encodeSequence(byte[] src, int anchor, int matchOff, int matchRef, in
184195
static int lastLiterals(byte[] src, int sOff, int srcLen, byte[] dest, int dOff, int destEnd) {
185196
final int runLen = srcLen;
186197

187-
if (dOff + runLen + 1 + (runLen + 255 - RUN_MASK) / 255 > destEnd) {
198+
if (notEnoughSpace(destEnd - dOff, 1 + lengthOfEncodedInteger(runLen) + runLen)) {
188199
throw new LZ4Exception();
189200
}
190201

0 commit comments

Comments
 (0)