Skip to content

Commit b65a3ce

Browse files
authored
Safely handle Files.exist on discovery and config when a Security Manager is present (#10009)
* Safely handle Files.exist on discovery adn config * Add filesystem-utils only to shared
1 parent 9d80781 commit b65a3ce

File tree

11 files changed

+125
-3
lines changed

11 files changed

+125
-3
lines changed

communication/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies {
1313
implementation(project(":remote-config:remote-config-core"))
1414
implementation(project(":internal-api"))
1515
implementation(project(":utils:container-utils"))
16+
implementation(project(":utils:filesystem-utils"))
1617
implementation(project(":utils:socket-utils"))
1718
implementation(project(":utils:version-utils"))
1819

communication/src/main/java/datadog/communication/monitor/DDAgentStatsDConnection.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.timgroup.statsd.NoOpDirectStatsDClient;
99
import com.timgroup.statsd.NonBlockingStatsDClientBuilder;
1010
import com.timgroup.statsd.StatsDClientErrorHandler;
11+
import datadog.common.filesystem.Files;
1112
import datadog.environment.OperatingSystem;
1213
import datadog.trace.api.Config;
1314
import datadog.trace.relocate.api.IOLogger;
@@ -186,7 +187,7 @@ private void discoverConnectionSettings() {
186187
}
187188

188189
if (null == host) {
189-
if (!OperatingSystem.isWindows() && new File(DEFAULT_DOGSTATSD_SOCKET_PATH).exists()) {
190+
if (!OperatingSystem.isWindows() && Files.exists(new File(DEFAULT_DOGSTATSD_SOCKET_PATH))) {
190191
log.info("Detected {}. Using it to send StatsD data.", DEFAULT_DOGSTATSD_SOCKET_PATH);
191192
host = DEFAULT_DOGSTATSD_SOCKET_PATH;
192193
port = 0; // tells dogstatsd client to treat host as a socket path

gradle/dependencies.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ final class CachedData {
2626
exclude(project(':telemetry'))
2727
exclude(project(':utils:config-utils'))
2828
exclude(project(':utils:container-utils'))
29+
exclude(project(':utils:filesystem-utils'))
2930
exclude(project(':utils:socket-utils'))
3031
exclude(project(':utils:time-utils'))
3132
exclude(project(':utils:version-utils'))

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ include(
155155
":dd-java-agent:testing",
156156
":utils:config-utils",
157157
":utils:container-utils",
158+
":utils:filesystem-utils",
158159
":utils:flare-utils",
159160
":utils:socket-utils",
160161
":utils:test-agent-utils:decoder",

utils/config-utils/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ dependencies {
5757
implementation(project(":components:environment"))
5858
implementation(project(":components:yaml"))
5959
implementation(project(":dd-trace-api"))
60+
implementation(project(":utils:filesystem-utils"))
6061
implementation(libs.slf4j)
6162

6263
testImplementation(project(":utils:test-utils"))

utils/config-utils/src/main/java/datadog/trace/bootstrap/config/provider/StableConfigSource.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static datadog.trace.util.ConfigStrings.propertyNameToEnvironmentVariableName;
44

5+
import datadog.common.filesystem.Files;
56
import datadog.trace.api.ConfigOrigin;
67
import datadog.trace.bootstrap.config.provider.stableconfig.StableConfigMappingException;
78
import java.io.File;
@@ -33,7 +34,7 @@ public final class StableConfigSource extends ConfigProvider.Source {
3334
try {
3435
File file = new File(filePath);
3536
log.debug("Stable configuration file found at path: {}", file);
36-
if (file.exists()) {
37+
if (Files.exists(file)) {
3738
cfg = StableConfigParser.parse(filePath);
3839
}
3940
} catch (Throwable e) {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
plugins {
2+
`java-library`
3+
}
4+
5+
apply(from = "$rootDir/gradle/java.gradle")
6+
7+
dependencies {
8+
testImplementation(project(":utils:test-utils"))
9+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package datadog.common.filesystem;
2+
3+
import java.io.File;
4+
import javax.annotation.Nonnull;
5+
6+
/** Utility methods related to file operations. */
7+
public final class Files {
8+
9+
private Files() {
10+
// Prevent instantiation
11+
}
12+
13+
/**
14+
* Determines whether the given file exists on the filesystem.
15+
*
16+
* <p>This method wraps {@link File#exists()} and safely handles {@link SecurityException}s that
17+
* may occur if the caller does not have the required permissions to examine the file. In such
18+
* cases, this method returns {@code false}.
19+
*
20+
* @param file the file to check for existence; must not be {@code null}
21+
* @return {@code true} if the file exists and can be queried, {@code false} otherwise
22+
*/
23+
public static boolean exists(@Nonnull File file) {
24+
try {
25+
return file.exists();
26+
} catch (SecurityException ignored) {
27+
return false;
28+
}
29+
}
30+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package datadog.common.filesystem;
2+
3+
import static org.junit.jupiter.api.Assertions.assertFalse;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import datadog.environment.JavaVirtualMachine;
7+
import java.io.File;
8+
import java.io.IOException;
9+
import java.security.Permission;
10+
import org.junit.jupiter.api.Test;
11+
import org.junit.jupiter.api.condition.DisabledIf;
12+
13+
public class FilesTest {
14+
15+
private SecurityManager originalSM;
16+
17+
@Test
18+
void existsReturnsTrueWhenFileExistsAndIsAccessible() throws IOException {
19+
File file = File.createTempFile("test", "txt");
20+
file.deleteOnExit();
21+
22+
assertTrue(Files.exists(file));
23+
}
24+
25+
@Test
26+
void existsReturnsFalseWhenFileDoesNotExist() throws IOException {
27+
File file = File.createTempFile("missing", "txt");
28+
assertTrue(file.delete()); // ensure it does not exist
29+
30+
assertFalse(Files.exists(file));
31+
}
32+
33+
@Test
34+
@DisabledIf("isJava18OrLater")
35+
void existsReturnsFalseWhenSecurityManagerForbidsFileAccess() throws IOException {
36+
File file = File.createTempFile("test", "txt");
37+
file.deleteOnExit();
38+
39+
// --- install restrictive SecurityManager only in this test ---
40+
SecurityManager originalSM = System.getSecurityManager();
41+
42+
System.setSecurityManager(
43+
new SecurityManager() {
44+
@Override
45+
public void checkRead(String filePath) {
46+
// Deny only THIS file so classloading still works
47+
if (filePath.equals(file.getAbsolutePath())) {
48+
throw new SecurityException("Access denied");
49+
}
50+
}
51+
52+
@Override
53+
public void checkPermission(Permission perm) {
54+
// allow everything else
55+
}
56+
57+
@Override
58+
public void checkPermission(Permission perm, Object context) {
59+
// allow everything else
60+
}
61+
});
62+
63+
try {
64+
boolean result = Files.exists(file);
65+
assertFalse(result);
66+
} finally {
67+
// --- restore original security manager ---
68+
System.setSecurityManager(originalSM);
69+
}
70+
}
71+
72+
static boolean isJava18OrLater() {
73+
return JavaVirtualMachine.isJavaVersionAtLeast(18);
74+
}
75+
}

utils/socket-utils/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ extensions.getByName("tracerJava").withGroovyBuilder {
1616
dependencies {
1717
implementation(libs.slf4j)
1818
implementation(project(":internal-api"))
19+
implementation(project(":utils:filesystem-utils"))
1920
implementation(libs.jnr.unixsocket)
2021
testImplementation(files(sourceSets["main_java17"].output))
2122
}

0 commit comments

Comments
 (0)