Skip to content

Commit 802b317

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

File tree

3 files changed

+165
-5
lines changed

3 files changed

+165
-5
lines changed

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

+27-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
import org.opensearch.javaagent.bootstrap.AgentPolicy;
1212

1313
import java.lang.instrument.Instrumentation;
14+
import java.nio.channels.FileChannel;
1415
import java.nio.channels.SocketChannel;
16+
import java.nio.file.Files;
1517
import java.util.Map;
1618

1719
import net.bytebuddy.ByteBuddy;
@@ -55,12 +57,32 @@ public static void agentmain(String agentArguments, Instrumentation instrumentat
5557

5658
private static AgentBuilder createAgentBuilder(Instrumentation inst) throws Exception {
5759
final Junction<TypeDescription> systemType = ElementMatchers.isSubTypeOf(SocketChannel.class);
60+
final Junction<TypeDescription> pathType = ElementMatchers.isSubTypeOf(Files.class);
61+
final Junction<TypeDescription> fileChannelType = ElementMatchers.isSubTypeOf(FileChannel.class);
5862

59-
final AgentBuilder.Transformer transformer = (b, typeDescription, classLoader, module, pd) -> b.visit(
63+
final AgentBuilder.Transformer socketTransformer = (b, typeDescription, classLoader, module, pd) -> b.visit(
6064
Advice.to(SocketChannelInterceptor.class)
6165
.on(ElementMatchers.named("connect").and(ElementMatchers.not(ElementMatchers.isAbstract())))
6266
);
6367

68+
final AgentBuilder.Transformer fileTransformer = (b, typeDescription, classLoader, module, pd) -> b.visit(
69+
Advice.to(FileInterceptor.class)
70+
.on(
71+
ElementMatchers.named("delete")
72+
.or(ElementMatchers.named("deleteIfExists"))
73+
.or(ElementMatchers.named("createFile"))
74+
.or(ElementMatchers.named("createDirectories"))
75+
.or(ElementMatchers.named("createLink"))
76+
.or(ElementMatchers.named("move"))
77+
.or(ElementMatchers.named("copy"))
78+
.or(ElementMatchers.named("newByteChannel"))
79+
.or(ElementMatchers.named("open"))
80+
.or(ElementMatchers.named("write"))
81+
.or(ElementMatchers.named("read"))
82+
.or(ElementMatchers.isConstructor())
83+
)
84+
);
85+
6486
ClassInjector.UsingUnsafe.ofBootLoader()
6587
.inject(
6688
Map.of(
@@ -72,15 +94,15 @@ private static AgentBuilder createAgentBuilder(Instrumentation inst) throws Exce
7294
);
7395

7496
final ByteBuddy byteBuddy = new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE);
75-
final AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
97+
return new AgentBuilder.Default(byteBuddy).with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
7698
.with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
7799
.with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
78100
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
79101
.ignore(ElementMatchers.none())
80102
.type(systemType)
81-
.transform(transformer);
82-
83-
return agentBuilder;
103+
.transform(socketTransformer)
104+
.type(pathType.or(fileChannelType))
105+
.transform(fileTransformer);
84106
}
85107

86108
private static void initAgent(Instrumentation instrumentation) throws Exception {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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.FilePermission;
14+
import java.lang.reflect.Method;
15+
import java.nio.file.Path;
16+
import java.nio.file.Paths;
17+
import java.security.Policy;
18+
import java.security.ProtectionDomain;
19+
import java.util.List;
20+
21+
import net.bytebuddy.asm.Advice;
22+
23+
/**
24+
* FileInterceptor
25+
*/
26+
public class FileInterceptor {
27+
/**
28+
* FileInterceptor
29+
*/
30+
public FileInterceptor() {}
31+
32+
/**
33+
* Intercepts file operations
34+
*
35+
* @param args arguments
36+
* @param method method
37+
* @throws Exception exceptions
38+
*/
39+
@Advice.OnMethodEnter
40+
@SuppressWarnings("removal")
41+
@SuppressForbidden(reason = "Using FilePermissions directly is ok")
42+
public static void intercept(@Advice.AllArguments Object[] args, @Advice.Origin Method method) throws Exception {
43+
final Policy policy = AgentPolicy.getPolicy();
44+
if (policy == null) {
45+
return; /* noop */
46+
}
47+
48+
String filePath = null;
49+
if (args.length > 0 && args[0] instanceof String pathStr) {
50+
filePath = Paths.get(pathStr).toAbsolutePath().toString();
51+
} else if (args.length > 0 && args[0] instanceof Path path) {
52+
filePath = path.toAbsolutePath().toString();
53+
}
54+
55+
if (filePath == null) {
56+
return; // No valid file path found
57+
}
58+
59+
final StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
60+
final List<ProtectionDomain> callers = walker.walk(new StackCallerChainExtractor());
61+
62+
boolean isMutating = method.getName().startsWith("write")
63+
|| method.getName().equals("createFile")
64+
|| method.getName().equals("createDirectories")
65+
|| method.getName().equals("createLink")
66+
|| method.getName().equals("copy")
67+
|| method.getName().equals("move")
68+
|| method.getName().equals("newByteChannel");
69+
boolean isDelete = method.getName().equals("delete") || method.getName().equals("deleteIfExists");
70+
71+
// Check each permission separately
72+
for (final ProtectionDomain domain : callers) {
73+
// Handle FileChannel.open() separately to check read/write permissions properly
74+
if (method.getName().equals("open")) {
75+
if (!policy.implies(domain, new FilePermission(filePath, "read, write"))) {
76+
throw new SecurityException("Denied OPEN access to file: " + filePath + ", domain: " + domain);
77+
}
78+
}
79+
80+
// File mutating operations
81+
if (isMutating && !policy.implies(domain, new FilePermission(filePath, "write"))) {
82+
throw new SecurityException("Denied WRITE access to file: " + filePath + ", domain: " + domain);
83+
}
84+
85+
// File deletion operations
86+
if (isDelete && !policy.implies(domain, new FilePermission(filePath, "delete"))) {
87+
throw new SecurityException("Denied DELETE access to file: " + filePath + ", domain: " + domain);
88+
}
89+
}
90+
}
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
/*
10+
* Licensed to Elasticsearch under one or more contributor
11+
* license agreements. See the NOTICE file distributed with
12+
* this work for additional information regarding copyright
13+
* ownership. Elasticsearch licenses this file to you under
14+
* the Apache License, Version 2.0 (the "License"); you may
15+
* not use this file except in compliance with the License.
16+
* You may obtain a copy of the License at
17+
*
18+
* http://www.apache.org/licenses/LICENSE-2.0
19+
*
20+
* Unless required by applicable law or agreed to in writing,
21+
* software distributed under the License is distributed on an
22+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23+
* KIND, either express or implied. See the License for the
24+
* specific language governing permissions and limitations
25+
* under the License.
26+
*/
27+
28+
/*
29+
* Modifications Copyright OpenSearch Contributors. See
30+
* GitHub history for details.
31+
*/
32+
33+
package org.opensearch.javaagent;
34+
35+
import java.lang.annotation.ElementType;
36+
import java.lang.annotation.Retention;
37+
import java.lang.annotation.RetentionPolicy;
38+
import java.lang.annotation.Target;
39+
40+
/**
41+
* Annotation to suppress forbidden-apis errors inside a whole class, a method, or a field.
42+
*/
43+
@Retention(RetentionPolicy.CLASS)
44+
@Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
45+
@interface SuppressForbidden {
46+
String reason();
47+
}

0 commit comments

Comments
 (0)