Skip to content

Commit 27c3125

Browse files
committed
Change checkFileRead to throw IOException (elastic#139320)
* Change checkFileRead to throw IOException. We used to transform every IOException aside from NoSuchFileException into a NotEntitledException. However, denied entitlements never result in IOExceptions, and the only method that uses this functionality is Path.toRealPath which itself throws IOException, so there's no reason to interpose and transmute the exception into NotEntitledException. Just let 'er rip. * Change assert false -> throw new AssertionError
1 parent 3fa04b0 commit 27c3125

File tree

5 files changed

+108
-23
lines changed

5 files changed

+108
-23
lines changed

libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.io.FileDescriptor;
1616
import java.io.FileFilter;
1717
import java.io.FilenameFilter;
18+
import java.io.IOException;
1819
import java.io.InputStream;
1920
import java.io.OutputStream;
2021
import java.io.PrintStream;
@@ -59,7 +60,6 @@
5960
import java.nio.file.FileVisitOption;
6061
import java.nio.file.FileVisitor;
6162
import java.nio.file.LinkOption;
62-
import java.nio.file.NoSuchFileException;
6363
import java.nio.file.OpenOption;
6464
import java.nio.file.Path;
6565
import java.nio.file.WatchEvent;
@@ -92,6 +92,21 @@
9292

9393
/**
9494
* Contains one "check" method for each distinct JDK method we want to instrument.
95+
* <p>
96+
* Methods starting with {@code check$} (with the dollar sign) follow a strict naming convention
97+
* that allows them to be matched up directly against the corresponding target method or constructor
98+
* by {@code InstrumentationService}.
99+
* The naming convention uses dollar signs as separators,
100+
* which works nicely because JCL methods don't use dollar signs.
101+
* <p>
102+
* Methods not starting with {@code check$} (for example, {@link #checkPathToRealPath})
103+
* are processed by {@code DynamicInstrumentation} and follow a different convention.
104+
* They are matched up with the appropriate implementation classes at runtime
105+
* once we know what they are.
106+
* <p>
107+
* Some of these methods have a {@code throws} clause.
108+
* The rule is that the check method should not throw a checked exception
109+
* unless that exception is also thrown by the instrumented method under the same circumstances.
95110
*/
96111
@SuppressWarnings("unused") // Called from instrumentation code inserted by the Entitlements agent
97112
public interface EntitlementChecker {
@@ -1177,7 +1192,14 @@ void checkNewByteChannel(
11771192
void checkType(Class<?> callerClass, FileStore that);
11781193

11791194
// path
1180-
void checkPathToRealPath(Class<?> callerClass, Path that, LinkOption... options) throws NoSuchFileException;
1195+
1196+
/**
1197+
* From {@link Path#toRealPath(LinkOption...)}...
1198+
*
1199+
* @throws IOException
1200+
* if the file does not exist or an I/O error occurs
1201+
*/
1202+
void checkPathToRealPath(Class<?> callerClass, Path that, LinkOption... options) throws IOException;
11811203

11821204
void checkPathRegister(Class<?> callerClass, Path that, WatchService watcher, WatchEvent.Kind<?>... events);
11831205

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/ElasticsearchEntitlementChecker.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.FileDescriptor;
1919
import java.io.FileFilter;
2020
import java.io.FilenameFilter;
21+
import java.io.IOException;
2122
import java.io.InputStream;
2223
import java.io.OutputStream;
2324
import java.io.PrintStream;
@@ -66,7 +67,6 @@
6667
import java.nio.file.FileVisitOption;
6768
import java.nio.file.FileVisitor;
6869
import java.nio.file.LinkOption;
69-
import java.nio.file.NoSuchFileException;
7070
import java.nio.file.OpenOption;
7171
import java.nio.file.Path;
7272
import java.nio.file.StandardOpenOption;
@@ -2634,7 +2634,7 @@ public void checkType(Class<?> callerClass, FileStore that) {
26342634
}
26352635

26362636
@Override
2637-
public void checkPathToRealPath(Class<?> callerClass, Path that, LinkOption... options) throws NoSuchFileException {
2637+
public void checkPathToRealPath(Class<?> callerClass, Path that, LinkOption... options) throws IOException {
26382638
boolean followLinks = true;
26392639
for (LinkOption option : options) {
26402640
if (option == LinkOption.NOFOLLOW_LINKS) {

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyChecker.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement;
1414

1515
import java.io.File;
16+
import java.io.IOException;
1617
import java.net.JarURLConnection;
1718
import java.net.URL;
1819
import java.net.URLConnection;
19-
import java.nio.file.NoSuchFileException;
2020
import java.nio.file.Path;
2121

2222
/**
@@ -52,7 +52,7 @@ public interface PolicyChecker {
5252

5353
void checkFileRead(Class<?> callerClass, File file);
5454

55-
void checkFileRead(Class<?> callerClass, Path path, boolean followLinks) throws NoSuchFileException;
55+
void checkFileRead(Class<?> callerClass, Path path, boolean followLinks) throws IOException;
5656

5757
void checkFileRead(Class<?> callerClass, Path path);
5858

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImpl.java

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import java.net.URISyntaxException;
3535
import java.net.URL;
3636
import java.net.URLConnection;
37-
import java.nio.file.NoSuchFileException;
3837
import java.nio.file.Path;
3938
import java.nio.file.Paths;
4039
import java.util.List;
@@ -206,16 +205,13 @@ public void checkFileRead(Class<?> callerClass, File file) {
206205
public void checkFileRead(Class<?> callerClass, Path path) {
207206
try {
208207
checkFileRead(callerClass, path, false);
209-
} catch (NoSuchFileException e) {
210-
assert false : "NoSuchFileException should only be thrown when following links";
211-
var notEntitledException = new NotEntitledException(e.getMessage());
212-
notEntitledException.addSuppressed(e);
213-
throw notEntitledException;
208+
} catch (IOException e) {
209+
throw new AssertionError("IOException should be impossible unless following links", e);
214210
}
215211
}
216212

217213
@Override
218-
public void checkFileRead(Class<?> callerClass, Path path, boolean followLinks) throws NoSuchFileException {
214+
public void checkFileRead(Class<?> callerClass, Path path, boolean followLinks) throws IOException {
219215
if (isPathOnDefaultFilesystem(path) == false) {
220216
return;
221217
}
@@ -229,15 +225,9 @@ public void checkFileRead(Class<?> callerClass, Path path, boolean followLinks)
229225
Path realPath = null;
230226
boolean canRead = entitlements.fileAccess().canRead(path);
231227
if (canRead && followLinks) {
232-
try {
233-
realPath = path.toRealPath();
234-
if (realPath.equals(path) == false) {
235-
canRead = entitlements.fileAccess().canRead(realPath);
236-
}
237-
} catch (NoSuchFileException e) {
238-
throw e; // rethrow
239-
} catch (IOException e) {
240-
canRead = false;
228+
realPath = path.toRealPath();
229+
if (realPath.equals(path) == false) {
230+
canRead = entitlements.fileAccess().canRead(realPath);
241231
}
242232
}
243233

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImplTests.java

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,24 @@
99

1010
package org.elasticsearch.entitlement.runtime.policy;
1111

12+
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement;
13+
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.FileData;
1214
import org.elasticsearch.test.ESTestCase;
1315

1416
import java.io.IOException;
17+
import java.nio.file.Files;
18+
import java.nio.file.NoSuchFileException;
19+
import java.nio.file.Path;
20+
import java.util.List;
21+
import java.util.Map;
1522
import java.util.Set;
1623
import java.util.stream.Stream;
1724

1825
import static org.elasticsearch.entitlement.runtime.policy.PolicyManagerTests.NO_ENTITLEMENTS_MODULE;
1926
import static org.elasticsearch.entitlement.runtime.policy.PolicyManagerTests.TEST_PATH_LOOKUP;
27+
import static org.elasticsearch.entitlement.runtime.policy.PolicyManagerTests.createEmptyTestServerPolicy;
2028
import static org.elasticsearch.entitlement.runtime.policy.PolicyManagerTests.makeClassInItsOwnModule;
29+
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ;
2130

2231
public class PolicyCheckerImplTests extends ESTestCase {
2332
public void testRequestingClassFastPath() throws IOException, ClassNotFoundException {
@@ -54,8 +63,72 @@ public void testRequestingModuleWithStackWalk() throws IOException, ClassNotFoun
5463
);
5564
}
5665

66+
/**
67+
* Set up a situation where the file read entitlement check encounters
68+
* a strange {@link IOException} that is not {@link NoSuchFileException}.
69+
* Ensure that the IOException makes it out to the caller, rather than
70+
* being transformed into a NotEntitledException.
71+
*/
72+
public void testIOExceptionFollowingSymlink() throws IOException {
73+
Path dir = createTempDir();
74+
Path symlink = dir.resolve("symlink");
75+
Path allegedDir = dir.resolve("not_a_dir");
76+
Path target = allegedDir.resolve("target");
77+
Files.createFile(allegedDir); // Not a dir!
78+
Files.createSymbolicLink(symlink, target);
79+
80+
PathLookup testPathLookup = new PathLookup() {
81+
@Override
82+
public Path pidFile() {
83+
throw new UnsupportedOperationException();
84+
}
85+
86+
@Override
87+
public Stream<Path> getBaseDirPaths(BaseDir baseDir) {
88+
return Stream.empty();
89+
}
90+
91+
@Override
92+
public Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName) {
93+
throw new UnsupportedOperationException();
94+
}
95+
96+
@Override
97+
public boolean isPathOnDefaultFilesystem(Path path) {
98+
// We need this to be on the default filesystem or it will be trivially allowed
99+
return true;
100+
}
101+
};
102+
103+
var policyManager = new TestPolicyManager(
104+
createEmptyTestServerPolicy(),
105+
List.of(),
106+
Map.of(
107+
"testComponent",
108+
new Policy(
109+
"testPolicy",
110+
List.of(new Scope("testModule", List.of(new FilesEntitlement(List.of(FileData.ofPath(symlink, READ))))))
111+
)
112+
),
113+
c -> PolicyManager.PolicyScope.plugin("testComponent", "testModule"),
114+
testPathLookup,
115+
List.of(),
116+
List.of()
117+
);
118+
policyManager.setActive(true);
119+
policyManager.setTriviallyAllowingTestCode(false);
120+
121+
var checker = checker(NO_ENTITLEMENTS_MODULE, policyManager, testPathLookup);
122+
var exception = assertThrows(IOException.class, () -> checker.checkFileRead(getClass(), symlink, true));
123+
}
124+
57125
private static PolicyCheckerImpl checker(Module entitlementsModule) {
58-
return new PolicyCheckerImpl(Set.of(), entitlementsModule, null, TEST_PATH_LOOKUP);
126+
// TODO: TEST_PATH_LOOKUP is always null at this point!
127+
return checker(entitlementsModule, null, TEST_PATH_LOOKUP);
128+
}
129+
130+
private static PolicyCheckerImpl checker(Module entitlementsModule, PolicyManager policyManager, PathLookup testPathLookup) {
131+
return new PolicyCheckerImpl(Set.of(), entitlementsModule, policyManager, testPathLookup);
59132
}
60133

61134
}

0 commit comments

Comments
 (0)