Skip to content

Commit 15d27a1

Browse files
authored
Enhance Java Agent to intercept System::exit (#17746)
* Enhance Java Agent to intercept System::exit Signed-off-by: Andriy Redko <[email protected]> * Address code review comments Signed-off-by: Andriy Redko <[email protected]> --------- Signed-off-by: Andriy Redko <[email protected]>
1 parent 0eabc79 commit 15d27a1

File tree

11 files changed

+109
-7
lines changed

11 files changed

+109
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1414
- Added Kinesis support as a plugin for the pull-based ingestion ([#17615](https://github.com/opensearch-project/OpenSearch/pull/17615))
1515
- [Security Manager Replacement] Create initial Java Agent to intercept Socket::connect calls ([#17724](https://github.com/opensearch-project/OpenSearch/pull/17724))
1616
- Add ingestion management APIs for pause, resume and get ingestion state ([#17631](https://github.com/opensearch-project/OpenSearch/pull/17631))
17+
- [Security Manager Replacement] Enhance Java Agent to intercept System::exit ([#17746](https://github.com/opensearch-project/OpenSearch/pull/17746))
1718

1819
### Changed
1920
- Migrate BC libs to their FIPS counterparts ([#14912](https://github.com/opensearch-project/OpenSearch/pull/14912))

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ junit = "4.13.2"
7272
hamcrest = "2.1"
7373
mockito = "5.16.1"
7474
objenesis = "3.3"
75-
bytebuddy = "1.17.4"
75+
bytebuddy = "1.17.5"
7676

7777
# benchmark dependencies
7878
jmh = "1.35"

libs/agent-sm/agent/build.gradle

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ dependencies {
1313
implementation project(":libs:agent-sm:bootstrap")
1414
implementation "net.bytebuddy:byte-buddy:${versions.bytebuddy}"
1515
compileOnly "com.google.code.findbugs:jsr305:3.0.2"
16+
17+
testImplementation "junit:junit:${versions.junit}"
18+
testImplementation "org.hamcrest:hamcrest:${versions.hamcrest}"
1619
}
1720

1821
var bootClasspath = configurations.bootstrap.incoming.artifactView { }.files
@@ -35,8 +38,8 @@ compileJava {
3538
options.compilerArgs -= '-Werror'
3639
}
3740

38-
test.enabled = false
3941
testingConventions.enabled = false
42+
tasks.named('forbiddenApisTest').configure { onlyIf { false } }
4043

4144
tasks.named('forbiddenApisMain').configure {
4245
replaceSignatureFiles 'jdk-signatures'
@@ -62,3 +65,12 @@ thirdPartyAudit {
6265
tasks.named('validateNebulaPom') {
6366
dependsOn prepareAgent
6467
}
68+
69+
tasks.test {
70+
dependsOn prepareAgent
71+
jvmArgs += ["-javaagent:" + project.jar.archiveFile.get()]
72+
}
73+
74+
tasks.check {
75+
dependsOn test
76+
}

libs/agent-sm/agent/licenses/byte-buddy-1.17.4.jar.sha1

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
88450f120903b7e72470462cdbd2b75a3842223c

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,13 @@ private static AgentBuilder createAgentBuilder(Instrumentation inst) throws Exce
7777
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
7878
.ignore(ElementMatchers.none())
7979
.type(systemType)
80-
.transform(transformer);
80+
.transform(transformer)
81+
.type(ElementMatchers.is(java.lang.System.class))
82+
.transform(
83+
(b, typeDescription, classLoader, module, pd) -> b.visit(
84+
Advice.to(SystemExitInterceptor.class).on(ElementMatchers.named("exit"))
85+
)
86+
);
8187

8288
return agentBuilder;
8389
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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.lang.StackWalker.Option;
14+
15+
import net.bytebuddy.asm.Advice;
16+
17+
/**
18+
* {@link System#exit} interceptor
19+
*/
20+
public class SystemExitInterceptor {
21+
/**
22+
* SystemExitInterceptor
23+
*/
24+
public SystemExitInterceptor() {}
25+
26+
/**
27+
* Interceptor
28+
* @param code exit code
29+
* @throws Exception exceptions
30+
*/
31+
@Advice.OnMethodEnter()
32+
public static void intercept(int code) throws Exception {
33+
final StackWalker walker = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);
34+
final Class<?> caller = walker.getCallerClass();
35+
36+
if (!AgentPolicy.isClassThatCanExit(caller.getName())) {
37+
throw new SecurityException("The class " + caller + " is not allowed to call System::exit(" + code + ")");
38+
}
39+
}
40+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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+
import org.junit.BeforeClass;
13+
import org.junit.Test;
14+
15+
import java.security.Policy;
16+
import java.util.Set;
17+
18+
public class SystemExitInterceptorTests {
19+
@SuppressWarnings("removal")
20+
@BeforeClass
21+
public static void setUp() {
22+
AgentPolicy.setPolicy(new Policy() {
23+
}, Set.of(), new String[] { "worker.org.gradle.process.internal.worker.GradleWorkerMain" });
24+
}
25+
26+
@Test(expected = SecurityException.class)
27+
public void testSystemExitIsForbidden() {
28+
System.exit(0);
29+
}
30+
}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.security.Permission;
1414
import java.security.Policy;
1515
import java.security.ProtectionDomain;
16+
import java.util.Arrays;
1617
import java.util.Collections;
1718
import java.util.List;
1819
import java.util.Set;
@@ -27,6 +28,7 @@ public class AgentPolicy {
2728
private static final Logger LOGGER = Logger.getLogger(AgentPolicy.class.getName());
2829
private static volatile Policy policy;
2930
private static volatile Set<String> trustedHosts;
31+
private static volatile Set<String> classesThatCanExit;
3032

3133
private AgentPolicy() {}
3234

@@ -35,18 +37,20 @@ private AgentPolicy() {}
3537
* @param policy policy
3638
*/
3739
public static void setPolicy(Policy policy) {
38-
setPolicy(policy, Set.of());
40+
setPolicy(policy, Set.of(), new String[0]);
3941
}
4042

4143
/**
4244
* Set Agent policy
4345
* @param policy policy
4446
* @param trustedHosts trusted hosts
47+
* @param classesThatCanExit classed that are allowed to call {@link System#exit}
4548
*/
46-
public static void setPolicy(Policy policy, final Set<String> trustedHosts) {
49+
public static void setPolicy(Policy policy, final Set<String> trustedHosts, final String[] classesThatCanExit) {
4750
if (AgentPolicy.policy == null) {
4851
AgentPolicy.policy = policy;
4952
AgentPolicy.trustedHosts = Collections.unmodifiableSet(trustedHosts);
53+
AgentPolicy.classesThatCanExit = Arrays.stream(classesThatCanExit).collect(Collectors.toSet());
5054
LOGGER.info("Policy attached successfully: " + policy);
5155
} else {
5256
throw new SecurityException("The Policy has been set already: " + AgentPolicy.policy);
@@ -86,4 +90,13 @@ public static Policy getPolicy() {
8690
public static boolean isTrustedHost(String hostname) {
8791
return AgentPolicy.trustedHosts.contains(hostname);
8892
}
93+
94+
/**
95+
* Check if class is allowed to call {@link System#exit}
96+
* @param name class name
97+
* @return is class allowed to call {@link System#exit} or not
98+
*/
99+
public static boolean isClassThatCanExit(String name) {
100+
return AgentPolicy.classesThatCanExit.contains(name);
101+
}
89102
}

test/framework/licenses/byte-buddy-1.17.4.jar.sha1

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)