Skip to content

Commit a8a8100

Browse files
committed
Implement a java agent based file interceptor
Signed-off-by: Gulshan <[email protected]>
1 parent 48bb3ce commit a8a8100

File tree

2 files changed

+112
-5
lines changed

2 files changed

+112
-5
lines changed

libs/agent-sm/agent/src/main/java/org/opensearch/javaagent/Agent.java

+25-5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
import org.opensearch.javaagent.bootstrap.AgentPolicy;
1212

13+
import java.io.File;
14+
import java.io.FileInputStream;
15+
import java.io.FileOutputStream;
1316
import java.lang.instrument.Instrumentation;
1417
import java.nio.channels.SocketChannel;
1518
import java.util.Map;
@@ -56,11 +59,28 @@ public static void agentmain(String agentArguments, Instrumentation instrumentat
5659
private static AgentBuilder createAgentBuilder(Instrumentation inst) throws Exception {
5760
final Junction<TypeDescription> systemType = ElementMatchers.isSubTypeOf(SocketChannel.class);
5861

59-
final AgentBuilder.Transformer transformer = (b, typeDescription, classLoader, module, pd) -> b.visit(
62+
final Junction<TypeDescription> fileType = ElementMatchers.isSubTypeOf(File.class);
63+
final Junction<TypeDescription> fileInputStreamType = ElementMatchers.isSubTypeOf(FileInputStream.class);
64+
final Junction<TypeDescription> fileOutputStreamType = ElementMatchers.isSubTypeOf(FileOutputStream.class);
65+
66+
final AgentBuilder.Transformer socketTransformer = (b, typeDescription, classLoader, module, pd) -> b.visit(
6067
Advice.to(SocketChannelInterceptor.class)
6168
.on(ElementMatchers.named("connect").and(ElementMatchers.not(ElementMatchers.isAbstract())))
6269
);
6370

71+
final AgentBuilder.Transformer fileTransformer = (b, typeDescription, classLoader, module, pd) -> b.visit(
72+
Advice.to(FileInterceptor.class)
73+
.on(
74+
ElementMatchers.named("delete")
75+
.or(ElementMatchers.named("createNewFile"))
76+
.or(ElementMatchers.named("renameTo"))
77+
.or(ElementMatchers.named("mkdirs"))
78+
.or(ElementMatchers.named("mkdir"))
79+
.or(ElementMatchers.named("open"))
80+
.or(ElementMatchers.isConstructor())
81+
)
82+
);
83+
6484
ClassInjector.UsingUnsafe.ofBootLoader()
6585
.inject(
6686
Map.of(
@@ -72,15 +92,15 @@ private static AgentBuilder createAgentBuilder(Instrumentation inst) throws Exce
7292
);
7393

7494
final ByteBuddy byteBuddy = new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE);
75-
final AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
95+
return new AgentBuilder.Default(byteBuddy).with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
7696
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
7797
.with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
7898
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
7999
.ignore(ElementMatchers.none())
80100
.type(systemType)
81-
.transform(transformer);
82-
83-
return agentBuilder;
101+
.transform(socketTransformer)
102+
.type(fileType.or(fileInputStreamType).or(fileOutputStreamType))
103+
.transform(fileTransformer);
84104
}
85105

86106
private static void initAgent(Instrumentation instrumentation) throws Exception {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.javaagent;
10+
11+
import org.opensearch.javaagent.bootstrap.AgentPolicy;
12+
13+
import java.io.File;
14+
import java.io.FilePermission;
15+
import java.lang.reflect.Method;
16+
import java.security.Policy;
17+
import java.security.ProtectionDomain;
18+
import java.util.List;
19+
20+
import net.bytebuddy.asm.Advice;
21+
import net.bytebuddy.asm.Advice.Origin;
22+
23+
/**
24+
* FileInterceptor
25+
*/
26+
public class FileInterceptor {
27+
/**
28+
* FileInterceptor
29+
*/
30+
public FileInterceptor() {}
31+
32+
/**
33+
* Intercepts file operations
34+
* @param args arguments
35+
* @param method method
36+
* @throws Exception exceptions
37+
*/
38+
@Advice.OnMethodEnter
39+
@SuppressWarnings("removal")
40+
public static void intercept(@Advice.AllArguments Object[] args, @Origin Method method) throws Exception {
41+
final Policy policy = AgentPolicy.getPolicy();
42+
if (policy == null) {
43+
return; /* noop */
44+
}
45+
46+
String filePath = null;
47+
if (args.length > 0 && args[0] instanceof String path) {
48+
filePath = path;
49+
} else if (args.length > 0 && args[0] instanceof File file) {
50+
filePath = file.getAbsolutePath();
51+
}
52+
53+
if (filePath == null) {
54+
return; // No valid file path found
55+
}
56+
57+
final StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
58+
final List<ProtectionDomain> callers = walker.walk(new StackCallerChainExtractor());
59+
60+
boolean isMutating = method.getName().startsWith("write")
61+
|| method.getName().equals("createNewFile")
62+
|| method.getName().equals("mkdirs")
63+
|| method.getName().equals("mkdir")
64+
|| method.getName().equals("setWritable");
65+
boolean isDelete = method.getName().equals("delete");
66+
67+
// Check each permission separately
68+
for (final ProtectionDomain domain : callers) {
69+
// FileInputStream operations
70+
if (!policy.implies(domain, new FilePermission(filePath, "read"))) {
71+
throw new SecurityException("Denied READ access to file: " + filePath + ", domain: " + domain);
72+
}
73+
// FileOutputStream operations
74+
if (!policy.implies(domain, new FilePermission(filePath, "write"))) {
75+
throw new SecurityException("Denied WRITE access to file: " + filePath + ", domain: " + domain);
76+
}
77+
// File mutating operations
78+
if (isMutating && !policy.implies(domain, new FilePermission(filePath, "write"))) {
79+
throw new SecurityException("Denied WRITE (mutating) operation to file: " + filePath + ", domain: " + domain);
80+
}
81+
// File deletion operations
82+
if (isDelete && !policy.implies(domain, new FilePermission(filePath, "delete"))) {
83+
throw new SecurityException("Denied DELETE access to file: " + filePath + ", domain: " + domain);
84+
}
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)