1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl3.introspection;
18
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20 import static org.junit.jupiter.api.Assertions.assertNotNull;
21 import static org.junit.jupiter.api.Assertions.assertThrows;
22 import static org.junit.jupiter.api.Assertions.assertTrue;
23 import static org.junit.jupiter.api.Assertions.fail;
24
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.LinkedHashMap;
28 import java.util.List;
29 import java.util.Map;
30
31 import org.apache.commons.jexl3.JexlArithmetic;
32 import org.apache.commons.jexl3.JexlBuilder;
33 import org.apache.commons.jexl3.JexlContext;
34 import org.apache.commons.jexl3.JexlEngine;
35 import org.apache.commons.jexl3.JexlException;
36 import org.apache.commons.jexl3.JexlExpression;
37 import org.apache.commons.jexl3.JexlScript;
38 import org.apache.commons.jexl3.JexlTestCase;
39 import org.apache.commons.jexl3.MapContext;
40 import org.apache.commons.jexl3.annotations.NoJexl;
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43 import org.junit.jupiter.api.Test;
44
45
46
47
48 @SuppressWarnings({ "UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes" })
49 class SandboxTest extends JexlTestCase {
50 public abstract static class AbstractCallMeNot {
51 public @NoJexl String NONO = "should not be accessible!";
52
53 public String allowInherit() {
54 return "this is allowed";
55 }
56
57 @NoJexl
58 public void callMeNot() {
59 fail("should not be callable!");
60 }
61 }
62
63 public static class Arithmetic350 extends JexlArithmetic {
64
65 MapBuilder mb = new org.apache.commons.jexl3.internal.MapBuilder(3);
66
67 public Arithmetic350(final boolean astrict) {
68 super(astrict);
69 }
70
71 Map<?, ?> getLastMap() {
72 return (Map<Object, Object>) mb.create();
73 }
74
75 @Override
76 public MapBuilder mapBuilder(final int size, final boolean extended) {
77 return mb;
78 }
79 }
80
81 @NoJexl
82 public interface CantCallMe {
83 void tryMe();
84 }
85
86 public static class CantSeeMe {
87 public boolean doIt() {
88 return false;
89 }
90 }
91
92 public static class Foo extends AbstractCallMeNot implements CantCallMe, TryCallMe {
93 String name;
94 public String alias;
95
96 public Foo(final String name) {
97 this.name = name;
98 this.alias = name + "-alias";
99 }
100
101 public @NoJexl Foo(final String name, final String notcallable) {
102 throw new UnsupportedOperationException("should not be callable!");
103 }
104
105 @NoJexl
106 public String cantCallMe() {
107 throw new UnsupportedOperationException("should not be callable!");
108 }
109
110 public int doIt() {
111 return 42;
112 }
113
114 public String getName() {
115 return name;
116 }
117
118 public String Quux() {
119 return name + "-quux";
120 }
121
122 public void setName(final String name) {
123 this.name = name;
124 }
125
126 @Override
127 public void tryMe() {
128 throw new UnsupportedOperationException("should not be callable!");
129 }
130
131 @Override
132 public void tryMeARiver() {
133 throw new UnsupportedOperationException("should not be callable!");
134 }
135 }
136
137 public static class Foo386 implements SomeInterface {
138 @Override
139 public int bar() {
140 return 42;
141 }
142 }
143
144 public static class Foo42 {
145 public int getFoo() {
146 return 42;
147 }
148 }
149
150 public static class Foo43 extends Foo42 {
151 @Override
152 @NoJexl
153 public int getFoo() {
154 return 43;
155 }
156 }
157
158 public static class Foo44 extends Foo43 {
159 @Override
160 public int getFoo() {
161 return 44;
162 }
163 }
164
165 public abstract static class Operation {
166 protected final int base;
167
168 public Operation(final int sz) {
169 base = sz;
170 }
171
172 public abstract int nonCallable(int y);
173
174 public abstract int someOp(int x);
175 }
176
177 public static class Operation2 extends Operation {
178 public Operation2(final int sz) {
179 super(sz);
180 }
181
182 @Override
183 public int nonCallable(final int y) {
184 throw new UnsupportedOperationException("do NOT call");
185 }
186
187 @Override
188 public int someOp(final int x) {
189 return base + x;
190 }
191 }
192
193 public static class Quux386 extends Foo386 {
194 @Override
195 public int bar() {
196 return -42;
197 }
198 }
199
200 public interface SomeInterface {
201 int bar();
202 }
203
204 public interface TryCallMe {
205 @NoJexl
206 void tryMeARiver();
207 }
208
209 static final Log LOGGER = LogFactory.getLog(SandboxTest.class.getName());
210
211 public SandboxTest() {
212 super("SandboxTest");
213 }
214
215 @Test
216 void testCantSeeMe() {
217 final JexlContext jc = new MapContext();
218 final String expr = "foo.doIt()";
219 JexlScript script;
220 Object result;
221
222 final JexlSandbox sandbox = new JexlSandbox(false);
223 sandbox.allow(Foo.class.getName());
224 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
225
226 jc.set("foo", new CantSeeMe());
227 script = sjexl.createScript(expr);
228 assertThrows(JexlException.class, () -> script.execute(jc));
229
230 jc.set("foo", new Foo("42"));
231 result = script.execute(jc);
232 assertEquals(42, ((Integer) result).intValue());
233 }
234
235 @Test
236 void testCtorAllow() {
237 final String expr = "new('" + Foo.class.getName() + "', '42')";
238 JexlScript script;
239 Object result;
240
241 final JexlSandbox sandbox = new JexlSandbox();
242 sandbox.allow(Foo.class.getName()).execute("");
243 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
244
245 script = sjexl.createScript(expr);
246 result = script.execute(null);
247 assertEquals("42", ((Foo) result).getName());
248 }
249
250 @Test
251 void testCtorBlock() {
252 final String expr = "new('" + Foo.class.getName() + "', '42')";
253 final JexlScript script = JEXL.createScript(expr);
254 Object result;
255 result = script.execute(null);
256 assertEquals("42", ((Foo) result).getName());
257
258 final JexlSandbox sandbox = new JexlSandbox();
259 sandbox.block(Foo.class.getName()).execute("");
260 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
261
262 final JexlScript script2 = sjexl.createScript(expr);
263 assertThrows(JexlException.Method.class, () -> script2.execute(null), "ctor should not be accessible");
264
265 }
266
267 @Test
268 void testGetAllow() {
269 final Foo foo = new Foo("42");
270 final String expr = "foo.alias";
271 JexlScript script;
272 Object result;
273
274 final JexlSandbox sandbox = new JexlSandbox();
275 sandbox.allow(Foo.class.getName()).read("alias");
276 sandbox.get(Foo.class.getName()).read().alias("alias", "ALIAS");
277 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
278
279 script = sjexl.createScript(expr, "foo");
280 result = script.execute(null, foo);
281 assertEquals(foo.alias, result);
282
283 script = sjexl.createScript("foo.ALIAS", "foo");
284 result = script.execute(null, foo);
285 assertEquals(foo.alias, result);
286 }
287
288 @Test
289 void testGetBlock() {
290 final String expr = "foo.alias";
291 final JexlScript script = JEXL.createScript(expr, "foo");
292 final Foo foo = new Foo("42");
293 Object result;
294 result = script.execute(null, foo);
295 assertEquals(foo.alias, result);
296
297 final JexlSandbox sandbox = new JexlSandbox();
298 sandbox.block(Foo.class.getName()).read("alias");
299 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
300
301 final JexlScript script2 = sjexl.createScript(expr, "foo");
302 assertThrows(JexlException.Property.class, () -> script2.execute(null, foo), "alias should not be accessible");
303 }
304
305 @Test
306 void testGetNullKeyAllowed0() {
307 final JexlEngine jexl = new JexlBuilder().sandbox(new JexlSandbox(true)).create();
308 final JexlExpression expression = jexl.createExpression("{null : 'foo'}[null]");
309 final Object o = expression.evaluate(null);
310 assertEquals("foo", o);
311 }
312
313 @Test
314 void testGetNullKeyAllowed1() {
315 final JexlSandbox sandbox = new JexlSandbox(true, true);
316 final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", false, true, true);
317 p.read().add("quux");
318 final JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
319
320 final String q = "'quux'";
321 final JexlExpression expression = jexl.createExpression("{" + q + " : 'foo'}[" + q + "]");
322 assertTrue(assertThrows(JexlException.Property.class, () -> expression.evaluate(null), "should have blocked " + q).getMessage().contains("undefined"));
323
324
325 for (final String k : Arrays.asList("'foo'", "null")) {
326 final JexlExpression expression2 = jexl.createExpression("{" + k + " : 'foo'}[" + k + "]");
327 final Object o = expression2.evaluate(null);
328 assertEquals("foo", o);
329 }
330 }
331
332 @Test
333 void testGetNullKeyBlocked() {
334 final JexlSandbox sandbox = new JexlSandbox(true, true);
335 final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", false, true, true);
336 p.read().add(null);
337 p.read().add("quux");
338
339 final JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
340 final JexlExpression e0 = jexl.createExpression("{'bar' : 'foo'}['bar']");
341 final Object r0 = e0.evaluate(null);
342 assertEquals("foo", r0);
343
344 for (final String k : Arrays.asList("'quux'", "null")) {
345 final JexlExpression expression = jexl.createExpression("{" + k + " : 'foo'}[" + k + "]");
346 assertTrue(
347 assertThrows(JexlException.Property.class, () -> expression.evaluate(null), "should have blocked " + k).getMessage().contains("undefined"));
348 }
349 }
350
351 @Test
352 void testInheritedPermission0() {
353 final Foo386 foo = new Foo386();
354 final JexlSandbox sandbox = new JexlSandbox(false, true);
355 sandbox.permissions(SomeInterface.class.getName(), true, true, true, true);
356 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
357 final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
358 assertEquals(42, (int) someOp.execute(null, foo));
359 }
360
361 @Test
362 void testInheritedPermission1() {
363 final Quux386 foo = new Quux386();
364 final JexlSandbox sandbox = new JexlSandbox(false, true);
365 sandbox.permissions(Foo386.class.getName(), true, true, true, true);
366 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
367 final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
368 assertEquals(-42, (int) someOp.execute(null, foo));
369 }
370
371 @Test
372 void testMethodAllow() {
373 final Foo foo = new Foo("42");
374 final String expr = "foo.Quux()";
375 JexlScript script;
376 Object result;
377
378 final JexlSandbox sandbox = new JexlSandbox();
379 sandbox.allow(Foo.class.getName()).execute("Quux");
380 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
381
382 script = sjexl.createScript(expr, "foo");
383 result = script.execute(null, foo);
384 assertEquals(foo.Quux(), result);
385 }
386
387 @Test
388 void testMethodBlock() {
389 final String expr = "foo.Quux()";
390 final JexlScript script = JEXL.createScript(expr, "foo");
391 final Foo foo = new Foo("42");
392 Object result;
393 result = script.execute(null, foo);
394 assertEquals(foo.Quux(), result);
395
396 final JexlSandbox sandbox = new JexlSandbox();
397 sandbox.block(Foo.class.getName()).execute("Quux");
398 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
399
400 final JexlScript script2 = sjexl.createScript(expr, "foo");
401 assertThrows(JexlException.Method.class, () -> script2.execute(null, foo), "Quux should not be accessible");
402 }
403
404 @Test
405 void testMethodNoJexl() {
406 final Foo foo = new Foo("42");
407
408 final String[] exprs = {
409 "foo.cantCallMe()",
410 "foo.tryMe()",
411 "foo.tryMeARiver()",
412 "foo.callMeNot()",
413 "foo.NONO",
414 "new('org.apache.commons.jexl3.SandboxTest$Foo', 'one', 'two')" };
415
416 final JexlEngine sjexl = new JexlBuilder().strict(true).safe(false).create();
417 for (final String expr : exprs) {
418 final JexlScript script = sjexl.createScript(expr, "foo");
419 assertThrows(JexlException.class, () -> script.execute(null, foo), "should have not been possible");
420 }
421 }
422
423 @Test
424 void testNoJexl312() {
425 final JexlContext ctxt = new MapContext();
426 final JexlEngine sjexl = new JexlBuilder().safe(false).strict(true).create();
427 final JexlScript foo = sjexl.createScript("x.getFoo()", "x");
428 assertThrows(JexlException.class, () -> foo.execute(ctxt, new Foo44()));
429 }
430
431 @Test
432 void testNonInheritedPermission0() {
433 final Foo386 foo = new Foo386();
434 final JexlSandbox sandbox = new JexlSandbox(false, true);
435 sandbox.permissions(SomeInterface.class.getName(), false, true, true, true);
436 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
437 final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
438 assertThrows(JexlException.class, () -> someOp.execute(null, foo), "should not be possible");
439 }
440
441 @Test
442 void testNonInheritedPermission1() {
443 final Quux386 foo = new Quux386();
444 final JexlSandbox sandbox = new JexlSandbox(false, true);
445 sandbox.permissions(Foo386.class.getName(), false, true, true, true);
446 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
447 final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
448 assertThrows(JexlException.class, () -> someOp.execute(null, foo), "should not be possible");
449 }
450
451 @Test
452 void testRestrict() {
453 final JexlContext context = new MapContext();
454 context.set("System", System.class);
455 final JexlSandbox sandbox = new JexlSandbox();
456
457 sandbox.allow(System.class.getName()).execute("currentTimeMillis");
458
459 sandbox.block(java.io.File.class.getName()).execute("");
460 final JexlEngine sjexl = new JexlBuilder().permissions(JexlPermissions.UNRESTRICTED).sandbox(sandbox).safe(false).strict(true).create();
461 Object result;
462 final JexlScript script1 = sjexl.createScript("System.exit()");
463 assertThrows(JexlException.class, () -> script1.execute(context), "should not allow calling exit!");
464 final JexlScript script2 = sjexl.createScript("System.exit(1)");
465 assertThrows(JexlException.class, () -> script2.execute(context), "should not allow calling exit!");
466 final JexlScript script3 = sjexl.createScript("new('java.io.File', '/tmp/should-not-be-created')");
467 assertThrows(JexlException.class, () -> script3.execute(context), "should not allow creating a file");
468 final JexlScript script4 = sjexl.createScript("System.currentTimeMillis()");
469 result = script4.execute(context);
470 assertNotNull(result);
471 }
472
473 @Test
474 void testSandboxInherit0() {
475 Object result;
476 final JexlContext ctxt = null;
477 final List<String> foo = new ArrayList<>();
478 final JexlSandbox sandbox = new JexlSandbox(false, true);
479 sandbox.allow(List.class.getName());
480
481 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
482 final JexlScript method = sjexl.createScript("foo.add(y)", "foo", "y");
483 final JexlScript set = sjexl.createScript("foo[x] = y", "foo", "x", "y");
484 final JexlScript get = sjexl.createScript("foo[x]", "foo", "x");
485
486 result = method.execute(ctxt, foo, "nothing");
487 assertTrue((boolean) result);
488 result = get.execute(null, foo, Integer.valueOf(0));
489 assertEquals("nothing", result);
490 result = set.execute(null, foo, Integer.valueOf(0), "42");
491 assertEquals("42", result);
492 result = get.execute(null, foo, Integer.valueOf(0));
493 assertEquals("42", result);
494 }
495
496 @Test
497 void testSandboxInherit1() {
498 Object result;
499 final JexlContext ctxt = null;
500 final Operation2 foo = new Operation2(12);
501 final JexlSandbox sandbox = new JexlSandbox(false, true);
502 sandbox.allow(Operation.class.getName());
503 sandbox.block(Operation.class.getName()).execute("nonCallable");
504
505 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
506 final JexlScript someOp = sjexl.createScript("foo.someOp(y)", "foo", "y");
507 result = someOp.execute(ctxt, foo, Integer.valueOf(30));
508 assertEquals(42, (int) result);
509 final JexlScript nonCallable = sjexl.createScript("foo.nonCallable(y)", "foo", "y");
510 assertThrows(JexlException.class, () -> nonCallable.execute(null, foo, Integer.valueOf(0)));
511 }
512
513 @Test
514 void testSetAllow() {
515 final Foo foo = new Foo("42");
516 final String expr = "foo.alias = $0";
517 JexlScript script;
518 Object result;
519
520 final JexlSandbox sandbox = new JexlSandbox();
521 sandbox.allow(Foo.class.getName()).write("alias");
522 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
523
524 script = sjexl.createScript(expr, "foo", "$0");
525 result = script.execute(null, foo, "43");
526 assertEquals("43", result);
527 assertEquals("43", foo.alias);
528 }
529
530 @Test
531 void testSetBlock() {
532 final String expr = "foo.alias = $0";
533 final JexlScript script1 = JEXL.createScript(expr, "foo", "$0");
534 final Foo foo = new Foo("42");
535 Object result;
536 result = script1.execute(null, foo, "43");
537 assertEquals("43", result);
538
539 final JexlSandbox sandbox = new JexlSandbox();
540 sandbox.block(Foo.class.getName()).write("alias");
541 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
542
543 final JexlScript script2 = sjexl.createScript(expr, "foo", "$0");
544 assertThrows(JexlException.class, () -> script2.execute(null, foo, "43"), "alias should not be accessible");
545
546 }
547
548 @Test
549 void testSetNullKeyAllowed0() {
550 final Arithmetic350 a350 = new Arithmetic350(true);
551 final JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(new JexlSandbox(true)).create();
552 final JexlContext jc = new MapContext();
553 final JexlExpression expression = jexl.createExpression("{null : 'foo'}[null] = 'bar'");
554 expression.evaluate(jc);
555 final Map<?, ?> map = a350.getLastMap();
556 assertEquals("bar", map.get(null));
557 }
558
559 @Test
560 void testSetNullKeyAllowed1() {
561 final Arithmetic350 a350 = new Arithmetic350(true);
562 final JexlSandbox sandbox = new JexlSandbox(true, true);
563 final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", true, false, true);
564 p.write().add("quux");
565 final JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(sandbox).create();
566
567 final String q = "'quux'";
568 final JexlExpression expression1 = jexl.createExpression("{" + q + " : 'foo'}[" + q + "] = '42'");
569 assertTrue(assertThrows(JexlException.Property.class, () -> expression1.evaluate(null), "should have blocked " + q).getMessage().contains("undefined"));
570
571 JexlExpression expression2 = jexl.createExpression("{'bar' : 'foo'}['bar'] = '42'");
572 expression2.evaluate(null);
573 Map<?, ?> map = a350.getLastMap();
574 assertEquals("42", map.get("bar"));
575 map.clear();
576 expression2 = jexl.createExpression("{null : 'foo'}[null] = '42'");
577 expression2.evaluate(null);
578 map = a350.getLastMap();
579 assertEquals("42", map.get(null));
580 }
581
582 @Test
583 void testSetNullKeyBlocked() {
584 final Arithmetic350 a350 = new Arithmetic350(true);
585 final JexlSandbox sandbox = new JexlSandbox(true, true);
586 final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", true, false, true);
587 p.write().add(null);
588 p.write().add("quux");
589 final JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(sandbox).create();
590
591 final JexlExpression expression = jexl.createExpression("{'bar' : 'foo'}['bar'] = '42'");
592 expression.evaluate(null);
593 final Map<?, ?> map = a350.getLastMap();
594 assertEquals("42", map.get("bar"));
595
596 for (final String k : Arrays.asList("'quux'", "null")) {
597 final JexlExpression expression2 = jexl.createExpression("{" + k + " : 'foo'}[" + k + "] = '42'");
598 assertTrue(assertThrows(JexlException.Property.class, () -> expression2.evaluate(null), "should have blocked " + k).getMessage()
599 .contains("undefined"));
600 }
601 }
602 interface I{}
603 static class A implements I{}
604 static class B extends A{}
605
606 @Test
607 void testPermissionOrder() {
608
609 final JexlSandbox sandboxAB = new JexlSandbox(false, true);
610 sandboxAB.permissions(I.class.getName(), true, true, true, false);
611 assertEquals("allow{all}", sandboxAB.get(A.class.getName()).write().toString());
612 assertEquals("allow{all}", sandboxAB.get(B.class.getName()).write().toString());
613 final JexlSandbox sandboxBA = new JexlSandbox(false, true);
614 sandboxBA.permissions(I.class.getName(), true, true, true, false);
615 assertEquals("allow{all}", sandboxBA.get(B.class.getName()).write().toString());
616 assertEquals("allow{all}", sandboxBA.get(A.class.getName()).write().toString());
617 }
618
619 @Test
620 void testIssue424() {
621 final JexlSandbox sandbox = new JexlSandbox(false, true);
622 sandbox.permissions(Map.class.getName(), true, true, true, true);
623 final String jexlCode = "x.foo = 'bar'";
624 final JexlEngine engine = new JexlBuilder()
625 .sandbox(sandbox)
626 .safe(false)
627 .strict(true).create();
628 final JexlContext context = new MapContext();
629 final Map<String, Object> x = new LinkedHashMap<>();
630 context.set("x", x);
631 final Object result = engine.createScript(jexlCode).execute(context);
632 assertEquals("bar", result);
633 assertEquals("bar", x.get("foo"));
634 }
635
636 }