Skip to content

Commit 21a5054

Browse files
authored
Merge branch 'main' into 2025/04/11/fix-126628
2 parents 337f453 + 8563235 commit 21a5054

File tree

61 files changed

+1605
-439
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1605
-439
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.gradle.internal.dependencies.patches;
11+
12+
import org.objectweb.asm.ClassVisitor;
13+
import org.objectweb.asm.ClassWriter;
14+
15+
import java.util.Arrays;
16+
import java.util.HexFormat;
17+
import java.util.function.Function;
18+
19+
public final class PatcherInfo {
20+
private final String jarEntryName;
21+
private final byte[] classSha256;
22+
private final Function<ClassWriter, ClassVisitor> visitorFactory;
23+
24+
private PatcherInfo(String jarEntryName, byte[] classSha256, Function<ClassWriter, ClassVisitor> visitorFactory) {
25+
this.jarEntryName = jarEntryName;
26+
this.classSha256 = classSha256;
27+
this.visitorFactory = visitorFactory;
28+
}
29+
30+
/**
31+
* Creates a patcher info entry, linking a jar entry path name and its SHA256 digest to a patcher factory (a factory to create an ASM
32+
* visitor)
33+
*
34+
* @param jarEntryName the jar entry path, as a string
35+
* @param classSha256 the SHA256 digest of the class bytes, as a HEX string
36+
* @param visitorFactory the factory to create an ASM visitor from a ASM writer
37+
*/
38+
public static PatcherInfo classPatcher(String jarEntryName, String classSha256, Function<ClassWriter, ClassVisitor> visitorFactory) {
39+
return new PatcherInfo(jarEntryName, HexFormat.of().parseHex(classSha256), visitorFactory);
40+
}
41+
42+
boolean matches(byte[] otherClassSha256) {
43+
return Arrays.equals(this.classSha256, otherClassSha256);
44+
}
45+
46+
public String jarEntryName() {
47+
return jarEntryName;
48+
}
49+
50+
public byte[] classSha256() {
51+
return classSha256;
52+
}
53+
54+
public ClassVisitor createVisitor(ClassWriter classWriter) {
55+
return visitorFactory.apply(classWriter);
56+
}
57+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.gradle.internal.dependencies.patches;
11+
12+
import org.objectweb.asm.ClassReader;
13+
import org.objectweb.asm.ClassWriter;
14+
15+
import java.io.File;
16+
import java.io.FileOutputStream;
17+
import java.io.IOException;
18+
import java.io.InputStream;
19+
import java.security.MessageDigest;
20+
import java.security.NoSuchAlgorithmException;
21+
import java.util.ArrayList;
22+
import java.util.Collection;
23+
import java.util.Enumeration;
24+
import java.util.HexFormat;
25+
import java.util.Locale;
26+
import java.util.function.Function;
27+
import java.util.jar.JarEntry;
28+
import java.util.jar.JarFile;
29+
import java.util.jar.JarOutputStream;
30+
import java.util.stream.Collectors;
31+
32+
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
33+
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
34+
35+
public class Utils {
36+
37+
private static final MessageDigest SHA_256;
38+
39+
static {
40+
try {
41+
SHA_256 = MessageDigest.getInstance("SHA-256");
42+
} catch (NoSuchAlgorithmException e) {
43+
throw new RuntimeException(e);
44+
}
45+
}
46+
47+
private record MismatchInfo(String jarEntryName, String expectedClassSha256, String foundClassSha256) {
48+
@Override
49+
public String toString() {
50+
return "[class='"
51+
+ jarEntryName
52+
+ '\''
53+
+ ", expected='"
54+
+ expectedClassSha256
55+
+ '\''
56+
+ ", found='"
57+
+ foundClassSha256
58+
+ '\''
59+
+ ']';
60+
}
61+
}
62+
63+
/**
64+
* Patches the classes in the input JAR file, using the collection of patchers. Each patcher specifies a target class (its jar entry
65+
* name) and the SHA256 digest on the class bytes.
66+
* This digest is checked against the class bytes in the JAR, and if it does not match, an IllegalArgumentException is thrown.
67+
* If the input file does not contain all the classes to patch specified in the patcher info collection, an IllegalArgumentException
68+
* is also thrown.
69+
* @param inputFile the JAR file to patch
70+
* @param outputFile the output (patched) JAR file
71+
* @param patchers list of patcher info (classes to patch (jar entry name + optional SHA256 digest) and ASM visitor to transform them)
72+
*/
73+
public static void patchJar(File inputFile, File outputFile, Collection<PatcherInfo> patchers) {
74+
var classPatchers = patchers.stream().collect(Collectors.toMap(PatcherInfo::jarEntryName, Function.identity()));
75+
var mismatchedClasses = new ArrayList<MismatchInfo>();
76+
try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(outputFile))) {
77+
Enumeration<JarEntry> entries = jarFile.entries();
78+
while (entries.hasMoreElements()) {
79+
JarEntry entry = entries.nextElement();
80+
String entryName = entry.getName();
81+
// Add the entry to the new JAR file
82+
jos.putNextEntry(new JarEntry(entryName));
83+
84+
var classPatcher = classPatchers.remove(entryName);
85+
if (classPatcher != null) {
86+
byte[] classToPatch = jarFile.getInputStream(entry).readAllBytes();
87+
var classSha256 = SHA_256.digest(classToPatch);
88+
89+
if (classPatcher.matches(classSha256)) {
90+
ClassReader classReader = new ClassReader(classToPatch);
91+
ClassWriter classWriter = new ClassWriter(classReader, COMPUTE_MAXS | COMPUTE_FRAMES);
92+
classReader.accept(classPatcher.createVisitor(classWriter), 0);
93+
jos.write(classWriter.toByteArray());
94+
} else {
95+
mismatchedClasses.add(
96+
new MismatchInfo(
97+
classPatcher.jarEntryName(),
98+
HexFormat.of().formatHex(classPatcher.classSha256()),
99+
HexFormat.of().formatHex(classSha256)
100+
)
101+
);
102+
}
103+
} else {
104+
// Read the entry's data and write it to the new JAR
105+
try (InputStream is = jarFile.getInputStream(entry)) {
106+
is.transferTo(jos);
107+
}
108+
}
109+
jos.closeEntry();
110+
}
111+
} catch (IOException ex) {
112+
throw new RuntimeException(ex);
113+
}
114+
115+
if (mismatchedClasses.isEmpty() == false) {
116+
throw new IllegalArgumentException(
117+
String.format(
118+
Locale.ROOT,
119+
"""
120+
Error patching JAR [%s]: SHA256 digest mismatch (%s). This JAR was updated to a version that contains different \
121+
classes, for which this patcher was not designed. Please check if the patcher still \
122+
applies correctly, and update the SHA256 digest(s).""",
123+
inputFile.getName(),
124+
mismatchedClasses.stream().map(MismatchInfo::toString).collect(Collectors.joining())
125+
)
126+
);
127+
}
128+
129+
if (classPatchers.isEmpty() == false) {
130+
throw new IllegalArgumentException(
131+
String.format(
132+
Locale.ROOT,
133+
"error patching [%s]: the jar does not contain [%s]",
134+
inputFile.getName(),
135+
String.join(", ", classPatchers.keySet())
136+
)
137+
);
138+
}
139+
}
140+
}

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/HdfsClassPatcher.java

Lines changed: 65 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
package org.elasticsearch.gradle.internal.dependencies.patches.hdfs;
1111

12+
import org.elasticsearch.gradle.internal.dependencies.patches.PatcherInfo;
13+
import org.elasticsearch.gradle.internal.dependencies.patches.Utils;
1214
import org.gradle.api.artifacts.transform.CacheableTransform;
1315
import org.gradle.api.artifacts.transform.InputArtifact;
1416
import org.gradle.api.artifacts.transform.TransformAction;
@@ -20,52 +22,85 @@
2022
import org.gradle.api.tasks.Input;
2123
import org.gradle.api.tasks.Optional;
2224
import org.jetbrains.annotations.NotNull;
23-
import org.objectweb.asm.ClassReader;
24-
import org.objectweb.asm.ClassVisitor;
25-
import org.objectweb.asm.ClassWriter;
2625

2726
import java.io.File;
28-
import java.io.FileOutputStream;
29-
import java.io.IOException;
30-
import java.io.InputStream;
31-
import java.util.Enumeration;
32-
import java.util.HashMap;
3327
import java.util.List;
34-
import java.util.Locale;
35-
import java.util.Map;
36-
import java.util.function.Function;
37-
import java.util.jar.JarEntry;
38-
import java.util.jar.JarFile;
39-
import java.util.jar.JarOutputStream;
4028
import java.util.regex.Pattern;
4129

42-
import static java.util.Map.entry;
43-
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
44-
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
30+
import static org.elasticsearch.gradle.internal.dependencies.patches.PatcherInfo.classPatcher;
4531

4632
@CacheableTransform
4733
public abstract class HdfsClassPatcher implements TransformAction<HdfsClassPatcher.Parameters> {
4834

49-
record JarPatchers(String artifactTag, Pattern artifactPattern, Map<String, Function<ClassWriter, ClassVisitor>> jarPatchers) {}
35+
record JarPatchers(String artifactTag, Pattern artifactPattern, List<PatcherInfo> jarPatchers) {}
5036

5137
static final List<JarPatchers> allPatchers = List.of(
5238
new JarPatchers(
53-
"hadoop-common",
54-
Pattern.compile("hadoop-common-(?!.*tests)"),
55-
Map.ofEntries(
56-
entry("org/apache/hadoop/util/ShutdownHookManager.class", ShutdownHookManagerPatcher::new),
57-
entry("org/apache/hadoop/util/Shell.class", ShellPatcher::new),
58-
entry("org/apache/hadoop/security/UserGroupInformation.class", SubjectGetSubjectPatcher::new)
39+
"hadoop2-common",
40+
Pattern.compile("hadoop-common-2(?!.*tests)"),
41+
List.of(
42+
classPatcher(
43+
"org/apache/hadoop/util/ShutdownHookManager.class",
44+
"3912451f02da9199dae7dba3f1420e0d951067addabbb235e7551de52234a0ef",
45+
ShutdownHookManagerPatcher::new
46+
),
47+
classPatcher(
48+
"org/apache/hadoop/util/Shell.class",
49+
"60400dc800e7c3e1a5fc499793033d877f5319bbd7633fee05d5a1d96b947bbd",
50+
ShellPatcher::new
51+
),
52+
classPatcher(
53+
"org/apache/hadoop/security/UserGroupInformation.class",
54+
"218078b8c77838f93d015c843775985a71f3c7a8128e2a9394410f0cd1da5f53",
55+
SubjectGetSubjectPatcher::new
56+
)
57+
)
58+
),
59+
new JarPatchers(
60+
"hadoop3-common",
61+
Pattern.compile("hadoop-common-3(?!.*tests)"),
62+
List.of(
63+
classPatcher(
64+
"org/apache/hadoop/util/ShutdownHookManager.class",
65+
"7720e8545a02de6fd03f4170f0e471d1301ef73d7d6a09097bad361f9e31f819",
66+
ShutdownHookManagerPatcher::new
67+
),
68+
classPatcher(
69+
"org/apache/hadoop/util/Shell.class",
70+
"856d0b829cf550df826387af15fa1c772bc7d26d6461535b17b9d5114d308dc4",
71+
ShellPatcher::new
72+
),
73+
classPatcher(
74+
"org/apache/hadoop/security/UserGroupInformation.class",
75+
"52f5973f35a282908d48a573a03c04f240a22c9f6007d7c5e7852aff1c641420",
76+
SubjectGetSubjectPatcher::new
77+
)
5978
)
6079
),
6180
new JarPatchers(
6281
"hadoop-client-api",
6382
Pattern.compile("hadoop-client-api.*"),
64-
Map.ofEntries(
65-
entry("org/apache/hadoop/util/ShutdownHookManager.class", ShutdownHookManagerPatcher::new),
66-
entry("org/apache/hadoop/util/Shell.class", ShellPatcher::new),
67-
entry("org/apache/hadoop/security/UserGroupInformation.class", SubjectGetSubjectPatcher::new),
68-
entry("org/apache/hadoop/security/authentication/client/KerberosAuthenticator.class", SubjectGetSubjectPatcher::new)
83+
List.of(
84+
classPatcher(
85+
"org/apache/hadoop/util/ShutdownHookManager.class",
86+
"90641e0726fc9372479728ef9b7ae2be20fb7ab4cddd4938e55ffecadddd4d94",
87+
ShutdownHookManagerPatcher::new
88+
),
89+
classPatcher(
90+
"org/apache/hadoop/util/Shell.class",
91+
"8837c7f3eeda3f658fc3d6595f18e77a4558220ff0becdf3e175fa4397a6fd0c",
92+
ShellPatcher::new
93+
),
94+
classPatcher(
95+
"org/apache/hadoop/security/UserGroupInformation.class",
96+
"3c34bbc2716a6c8f4e356e78550599b0a4f01882712b4f7787d032fb10527212",
97+
SubjectGetSubjectPatcher::new
98+
),
99+
classPatcher(
100+
"org/apache/hadoop/security/authentication/client/KerberosAuthenticator.class",
101+
"6bab26c1032a38621c20050ec92067226d1d67972d0d370e412ca25f1df96b76",
102+
SubjectGetSubjectPatcher::new
103+
)
69104
)
70105
)
71106
);
@@ -95,55 +130,9 @@ public void transform(@NotNull TransformOutputs outputs) {
95130
} else {
96131
patchersToApply.forEach(patchers -> {
97132
System.out.println("Patching " + inputFile.getName());
98-
99-
Map<String, Function<ClassWriter, ClassVisitor>> jarPatchers = new HashMap<>(patchers.jarPatchers());
100133
File outputFile = outputs.file(inputFile.getName().replace(".jar", "-patched.jar"));
101-
102-
patchJar(inputFile, outputFile, jarPatchers);
103-
104-
if (jarPatchers.isEmpty() == false) {
105-
throw new IllegalArgumentException(
106-
String.format(
107-
Locale.ROOT,
108-
"error patching [%s] with [%s]: the jar does not contain [%s]",
109-
inputFile.getName(),
110-
patchers.artifactPattern().toString(),
111-
String.join(", ", jarPatchers.keySet())
112-
)
113-
);
114-
}
134+
Utils.patchJar(inputFile, outputFile, patchers.jarPatchers());
115135
});
116136
}
117137
}
118-
119-
private static void patchJar(File inputFile, File outputFile, Map<String, Function<ClassWriter, ClassVisitor>> jarPatchers) {
120-
try (JarFile jarFile = new JarFile(inputFile); JarOutputStream jos = new JarOutputStream(new FileOutputStream(outputFile))) {
121-
Enumeration<JarEntry> entries = jarFile.entries();
122-
while (entries.hasMoreElements()) {
123-
JarEntry entry = entries.nextElement();
124-
String entryName = entry.getName();
125-
// Add the entry to the new JAR file
126-
jos.putNextEntry(new JarEntry(entryName));
127-
128-
Function<ClassWriter, ClassVisitor> classPatcher = jarPatchers.remove(entryName);
129-
if (classPatcher != null) {
130-
byte[] classToPatch = jarFile.getInputStream(entry).readAllBytes();
131-
132-
ClassReader classReader = new ClassReader(classToPatch);
133-
ClassWriter classWriter = new ClassWriter(classReader, COMPUTE_FRAMES | COMPUTE_MAXS);
134-
classReader.accept(classPatcher.apply(classWriter), 0);
135-
136-
jos.write(classWriter.toByteArray());
137-
} else {
138-
// Read the entry's data and write it to the new JAR
139-
try (InputStream is = jarFile.getInputStream(entry)) {
140-
is.transferTo(jos);
141-
}
142-
}
143-
jos.closeEntry();
144-
}
145-
} catch (IOException ex) {
146-
throw new RuntimeException(ex);
147-
}
148-
}
149138
}

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/hdfs/SubjectGetSubjectPatcher.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class SubjectGetSubjectPatcher extends ClassVisitor {
2525

2626
@Override
2727
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
28-
return new ReplaceCallMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions), name, access, descriptor);
28+
return new ReplaceCallMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions));
2929
}
3030

3131
/**
@@ -35,7 +35,7 @@ private static class ReplaceCallMethodVisitor extends MethodVisitor {
3535
private static final String SUBJECT_CLASS_INTERNAL_NAME = "javax/security/auth/Subject";
3636
private static final String METHOD_NAME = "getSubject";
3737

38-
ReplaceCallMethodVisitor(MethodVisitor methodVisitor, String name, int access, String descriptor) {
38+
ReplaceCallMethodVisitor(MethodVisitor methodVisitor) {
3939
super(ASM9, methodVisitor);
4040
}
4141

0 commit comments

Comments
 (0)