Skip to content

Commit d07767c

Browse files
authored
Change checkFileRead to throw IOException (#139320) (#139332)
* 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 cee7566 commit d07767c

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;
@@ -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.WatchEvent;
@@ -100,6 +100,21 @@
100100

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

12451260
// path
1246-
void checkPathToRealPath(Class<?> callerClass, Path that, LinkOption... options) throws NoSuchFileException;
1261+
1262+
/**
1263+
* From {@link Path#toRealPath(LinkOption...)}...
1264+
*
1265+
* @throws IOException
1266+
* if the file does not exist or an I/O error occurs
1267+
*/
1268+
void checkPathToRealPath(Class<?> callerClass, Path that, LinkOption... options) throws IOException;
12471269

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

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;
@@ -73,7 +74,6 @@
7374
import java.nio.file.FileVisitOption;
7475
import java.nio.file.FileVisitor;
7576
import java.nio.file.LinkOption;
76-
import java.nio.file.NoSuchFileException;
7777
import java.nio.file.OpenOption;
7878
import java.nio.file.Path;
7979
import java.nio.file.StandardOpenOption;
@@ -2737,7 +2737,7 @@ public void checkType(Class<?> callerClass, FileStore that) {
27372737
}
27382738

27392739
@Override
2740-
public void checkPathToRealPath(Class<?> callerClass, Path that, LinkOption... options) throws NoSuchFileException {
2740+
public void checkPathToRealPath(Class<?> callerClass, Path that, LinkOption... options) throws IOException {
27412741
boolean followLinks = true;
27422742
for (LinkOption option : options) {
27432743
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)