Skip to content

Commit bb66f3b

Browse files
authored
Explicitly reject duplicate data paths
Duplicate data paths already fail to work because we would attempt to take out a node lock on the directory a second time which will fail after the first lock attempt succeeds. However, how this failure manifests is not apparent at all and is quite difficult to debug. Instead, we should explicitly reject duplicate data paths to make the failure cause more obvious. Relates #25178
1 parent 982900e commit bb66f3b

File tree

2 files changed

+47
-4
lines changed

2 files changed

+47
-4
lines changed

core/src/main/java/org/elasticsearch/bootstrap/Security.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import java.security.URIParameter;
4747
import java.util.Collections;
4848
import java.util.HashMap;
49+
import java.util.HashSet;
4950
import java.util.LinkedHashSet;
5051
import java.util.Map;
5152
import java.util.Set;
@@ -262,8 +263,22 @@ static void addFilePermissions(Permissions policy, Environment environment) {
262263
if (environment.sharedDataFile() != null) {
263264
addPath(policy, Environment.PATH_SHARED_DATA_SETTING.getKey(), environment.sharedDataFile(), "read,readlink,write,delete");
264265
}
266+
final Set<Path> dataFilesPaths = new HashSet<>();
265267
for (Path path : environment.dataFiles()) {
266268
addPath(policy, Environment.PATH_DATA_SETTING.getKey(), path, "read,readlink,write,delete");
269+
/*
270+
* We have to do this after adding the path because a side effect of that is that the directory is created; the Path#toRealPath
271+
* invocation will fail if the directory does not already exist. We use Path#toRealPath to follow symlinks and handle issues
272+
* like unicode normalization or case-insensitivity on some filesystems (e.g., the case-insensitive variant of HFS+ on macOS).
273+
*/
274+
try {
275+
final Path realPath = path.toRealPath();
276+
if (!dataFilesPaths.add(realPath)) {
277+
throw new IllegalStateException("path [" + realPath + "] is duplicated by [" + path + "]");
278+
}
279+
} catch (final IOException e) {
280+
throw new IllegalStateException("unable to access [" + path + "]", e);
281+
}
267282
}
268283
/*
269284
* If path.data and default.path.data are set, we need read access to the paths in default.path.data to check for the existence of
@@ -392,11 +407,12 @@ private static void addSocketPermissionForPortRange(final Permissions policy, fi
392407
}
393408

394409
/**
395-
* Add access to path (and all files underneath it)
396-
* @param policy current policy to add permissions to
410+
* Add access to path (and all files underneath it); this also creates the directory if it does not exist.
411+
*
412+
* @param policy current policy to add permissions to
397413
* @param configurationName the configuration name associated with the path (for error messages only)
398-
* @param path the path itself
399-
* @param permissions set of file permissions to grant to the path
414+
* @param path the path itself
415+
* @param permissions set of file permissions to grant to the path
400416
*/
401417
static void addPath(Permissions policy, String configurationName, Path path, String permissions) {
402418
// paths may not exist yet, this also checks accessibility

qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/EvilSecurityTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
import java.security.Permissions;
3636
import java.util.Set;
3737

38+
import static org.hamcrest.Matchers.containsString;
39+
import static org.hamcrest.Matchers.hasToString;
40+
3841
@SuppressForbidden(reason = "modifies system properties and attempts to create symbolic links intentionally")
3942
public class EvilSecurityTests extends ESTestCase {
4043

@@ -135,6 +138,30 @@ public void testEnvironmentPaths() throws Exception {
135138
assertExactPermissions(new FilePermission(environment.pidFile().toString(), "delete"), permissions);
136139
}
137140

141+
public void testDuplicateDataPaths() throws IOException {
142+
final Path path = createTempDir();
143+
final Path home = path.resolve("home");
144+
final Path data = path.resolve("data");
145+
final Path duplicate;
146+
if (randomBoolean()) {
147+
duplicate = data;
148+
} else {
149+
duplicate = createTempDir().toAbsolutePath().resolve("link");
150+
Files.createSymbolicLink(duplicate, data);
151+
}
152+
153+
final Settings settings =
154+
Settings
155+
.builder()
156+
.put(Environment.PATH_HOME_SETTING.getKey(), home.toString())
157+
.putArray(Environment.PATH_DATA_SETTING.getKey(), data.toString(), duplicate.toString())
158+
.build();
159+
160+
final Environment environment = new Environment(settings);
161+
final IllegalStateException e = expectThrows(IllegalStateException.class, () -> Security.createPermissions(environment));
162+
assertThat(e, hasToString(containsString("path [" + duplicate.toRealPath() + "] is duplicated by [" + duplicate + "]")));
163+
}
164+
138165
public void testEnsureSymlink() throws IOException {
139166
Path p = createTempDir();
140167

0 commit comments

Comments
 (0)