Skip to content

Commit

Permalink
Add attempt to resolve installer from non-obfuscated name to avoid du…
Browse files Browse the repository at this point in the history
…plicated resolutions.
  • Loading branch information
raphw committed Jun 18, 2024
1 parent f321574 commit 21044eb
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 62 deletions.
128 changes: 66 additions & 62 deletions byte-buddy-agent/src/main/java/net/bytebuddy/agent/ByteBuddyAgent.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ public class ByteBuddyAgent {
*/
public static final String LATENT_RESOLVE = "net.bytebuddy.agent.latent";

/**
* The name of the {@link Installer} class that is stored in an obfuscated format which will not be relocated.
*/
private static final String INSTALLER_NAME = new StringBuilder("rellatsnI.tnega.yddubetyb.ten").reverse().toString();

/**
* The manifest property specifying the agent class.
*/
Expand All @@ -90,21 +95,6 @@ public class ByteBuddyAgent {
*/
private static final String MANIFEST_VERSION_VALUE = "1.0";

/**
* The size of the buffer for copying the agent installer file into another jar.
*/
private static final int BUFFER_SIZE = 1024 * 8;

/**
* Convenience indices for reading and writing to the buffer to make the code more readable.
*/
private static final int START_INDEX = 0, END_OF_FILE = -1;

/**
* The status code expected as a result of a successful attachment.
*/
private static final int SUCCESSFUL_ATTACH = 0;

/**
* Representation of the bootstrap {@link java.lang.ClassLoader}.
*/
Expand Down Expand Up @@ -152,28 +142,11 @@ public class ByteBuddyAgent {
*/
private static final String OS_NAME = "os.name";

/**
* The name of the method for reading the installer's instrumentation.
*/
private static final String INSTRUMENTATION_METHOD = "getInstrumentation";

/**
* Represents the {@code file} URL protocol.
*/
private static final String FILE_PROTOCOL = "file";

/**
* An indicator variable to express that no instrumentation is available.
*/
@AlwaysNull
private static final Instrumentation UNAVAILABLE = null;

/**
* Represents a failed attempt to self-resolve a jar file location.
*/
@AlwaysNull
private static final File CANNOT_SELF_RESOLVE = null;

/**
* The attachment type evaluator to be used for determining if an attachment requires an external process.
*/
Expand Down Expand Up @@ -672,10 +645,10 @@ private static void installExternal(AttachmentProvider.Accessor.ExternalAttachme
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(attachmentJar));
try {
jarOutputStream.putNextEntry(new JarEntry(Attacher.class.getName().replace('.', '/') + CLASS_FILE_EXTENSION));
byte[] buffer = new byte[BUFFER_SIZE];
byte[] buffer = new byte[1024 * 8];
int index;
while ((index = inputStream.read(buffer)) != END_OF_FILE) {
jarOutputStream.write(buffer, START_INDEX, index);
while ((index = inputStream.read(buffer)) != -1) {
jarOutputStream.write(buffer, 0, index);
}
jarOutputStream.closeEntry();
} finally {
Expand All @@ -702,7 +675,7 @@ private static void installExternal(AttachmentProvider.Accessor.ExternalAttachme
processId,
agent.getAbsolutePath(),
Boolean.toString(isNative),
argument == null ? "" : (AGENT_ARGUMENT_SEPARATOR + argument)).start().waitFor() != SUCCESSFUL_ATTACH) {
argument == null ? "" : (AGENT_ARGUMENT_SEPARATOR + argument)).start().waitFor() != 0) {
throw new IllegalStateException("Could not self-attach to current VM using external process - set a property "
+ Attacher.DUMP_PROPERTY
+ " to dump the process output to a file at the specified location");
Expand All @@ -726,31 +699,31 @@ private static void installExternal(AttachmentProvider.Accessor.ExternalAttachme
private static File trySelfResolve() {
try {
if (Boolean.getBoolean(LATENT_RESOLVE)) {
return CANNOT_SELF_RESOLVE;
return null;
}
ProtectionDomain protectionDomain = Attacher.class.getProtectionDomain();
if (protectionDomain == null) {
return CANNOT_SELF_RESOLVE;
return null;
}
CodeSource codeSource = protectionDomain.getCodeSource();
if (codeSource == null) {
return CANNOT_SELF_RESOLVE;
return null;
}
URL location = codeSource.getLocation();
if (!location.getProtocol().equals(FILE_PROTOCOL)) {
return CANNOT_SELF_RESOLVE;
return null;
}
try {
File file = new File(location.toURI());
if (file.getPath().contains(AGENT_ARGUMENT_SEPARATOR)) {
return CANNOT_SELF_RESOLVE;
return null;
}
return file;
} catch (URISyntaxException ignored) {
return new File(location.getPath());
}
} catch (Exception ignored) {
return CANNOT_SELF_RESOLVE;
return null;
}
}

Expand All @@ -761,10 +734,28 @@ private static File trySelfResolve() {
* @return The Byte Buddy agent's {@link java.lang.instrument.Instrumentation} instance.
*/
@MaybeNull
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.")
private static Instrumentation doGetInstrumentation() {
if (!INSTALLER_NAME.equals(Installer.class.getName())) {
Instrumentation instrumentation = doGetInstrumentation(INSTALLER_NAME);
if (instrumentation != null) {
return instrumentation;
}
}
return doGetInstrumentation(Installer.class.getName());
}

/**
* Performs the actual lookup of the {@link java.lang.instrument.Instrumentation} from an installed
* Byte Buddy agent and returns the instance, or returns {@code null} if not present.
*
* @param name The name of the {@link Installer} class which might be shaded.
* @return The Byte Buddy agent's {@link java.lang.instrument.Instrumentation} instance.
*/
@MaybeNull
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.")
private static Instrumentation doGetInstrumentation(String name) {
try {
Class<?> installer = Class.forName(Installer.class.getName(), true, ClassLoader.getSystemClassLoader());
Class<?> installer = Class.forName(name, true, ClassLoader.getSystemClassLoader());
try {
Class<?> module = Class.forName("java.lang.Module");
Method getModule = Class.class.getMethod("getModule");
Expand All @@ -775,11 +766,11 @@ private static Instrumentation doGetInstrumentation() {
} catch (ClassNotFoundException ignored) {
/* empty */
}
return (Instrumentation) Class.forName(Installer.class.getName(), true, ClassLoader.getSystemClassLoader())
.getMethod(INSTRUMENTATION_METHOD)
return (Instrumentation) Class.forName(name, true, ClassLoader.getSystemClassLoader())
.getMethod("getInstrumentation")
.invoke(null);
} catch (Exception ignored) {
return UNAVAILABLE;
return null;
}
}

Expand Down Expand Up @@ -1447,25 +1438,26 @@ enum ForByteBuddyAgent implements AgentProvider {
* to avoid the creation of a temporary jar file which can remain undeleted on Windows operating systems where the agent
* is linked by a class loader such that {@link File#deleteOnExit()} does not have an effect.
*
* @param installer The installer class to attempt to resolve which might be a shaded version of the class.
* @return This jar file's location or {@code null} if this jar file's location is inaccessible.
* @throws IOException If an I/O exception occurs.
*/
@MaybeNull
private static File trySelfResolve() throws IOException {
ProtectionDomain protectionDomain = Installer.class.getProtectionDomain();
private static File trySelfResolve(Class<?> installer) throws IOException {
ProtectionDomain protectionDomain = installer.getProtectionDomain();
if (Boolean.getBoolean(LATENT_RESOLVE)) {
return CANNOT_SELF_RESOLVE;
return null;
}
if (protectionDomain == null) {
return CANNOT_SELF_RESOLVE;
return null;
}
CodeSource codeSource = protectionDomain.getCodeSource();
if (codeSource == null) {
return CANNOT_SELF_RESOLVE;
return null;
}
URL location = codeSource.getLocation();
if (!location.getProtocol().equals(FILE_PROTOCOL)) {
return CANNOT_SELF_RESOLVE;
return null;
}
File agentJar;
try {
Expand All @@ -1474,26 +1466,26 @@ private static File trySelfResolve() throws IOException {
agentJar = new File(location.getPath());
}
if (!agentJar.isFile() || !agentJar.canRead()) {
return CANNOT_SELF_RESOLVE;
return null;
}
// It is necessary to check the manifest of the containing file as this code can be shaded into another artifact.
JarInputStream jarInputStream = new JarInputStream(new FileInputStream(agentJar));
try {
Manifest manifest = jarInputStream.getManifest();
if (manifest == null) {
return CANNOT_SELF_RESOLVE;
return null;
}
Attributes attributes = manifest.getMainAttributes();
if (attributes == null) {
return CANNOT_SELF_RESOLVE;
return null;
}
if (Installer.class.getName().equals(attributes.getValue(AGENT_CLASS_PROPERTY))
if (installer.getName().equals(attributes.getValue(AGENT_CLASS_PROPERTY))
&& Boolean.parseBoolean(attributes.getValue(CAN_REDEFINE_CLASSES_PROPERTY))
&& Boolean.parseBoolean(attributes.getValue(CAN_RETRANSFORM_CLASSES_PROPERTY))
&& Boolean.parseBoolean(attributes.getValue(CAN_SET_NATIVE_METHOD_PREFIX))) {
return agentJar;
} else {
return CANNOT_SELF_RESOLVE;
return null;
}
} finally {
jarInputStream.close();
Expand Down Expand Up @@ -1523,10 +1515,10 @@ private static File createJarFile() throws IOException {
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(agentJar), manifest);
try {
jarOutputStream.putNextEntry(new JarEntry(Installer.class.getName().replace('.', '/') + CLASS_FILE_EXTENSION));
byte[] buffer = new byte[BUFFER_SIZE];
byte[] buffer = new byte[1024 * 8];
int index;
while ((index = inputStream.read(buffer)) != END_OF_FILE) {
jarOutputStream.write(buffer, START_INDEX, index);
while ((index = inputStream.read(buffer)) != -1) {
jarOutputStream.write(buffer, 0, index);
}
jarOutputStream.closeEntry();
} finally {
Expand All @@ -1543,7 +1535,19 @@ private static File createJarFile() throws IOException {
*/
public File resolve() throws IOException {
try {
File resolved = trySelfResolve();
if (!Installer.class.getName().equals(INSTALLER_NAME)) {
try {
File resolved = trySelfResolve(Class.forName(INSTALLER_NAME,
false,
ClassLoader.getSystemClassLoader()));
if (resolved != null) {
return resolved;
}
} catch (ClassNotFoundException ignored) {
/* do nothing */
}
}
File resolved = trySelfResolve(Installer.class);
if (resolved != null) {
return resolved;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.bytebuddy.agent;

import org.hamcrest.CoreMatchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
Expand Down Expand Up @@ -60,4 +61,12 @@ public void testConstructorThrowsException() throws Exception {
throw (Exception) exception.getTargetException();
}
}

@Test
public void testInstallerObfuscatedNameMatches() throws Exception {
Field field = ByteBuddyAgent.class.getDeclaredField("INSTALLER_NAME");
field.setAccessible(true);
Object value = field.get(null);
assertThat(value, CoreMatchers.is(Installer.class.getName()));
}
}

0 comments on commit 21044eb

Please sign in to comment.