1 /*
2  * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /*
25  * @test
26  * @summary Verifies that privileged operations performed in the record
27  *          constructor throw, when run without the required permissions
28  * @compile --enable-preview -source ${jdk.version} ConstructorPermissionTest.java
29  * @run testng/othervm/java.security.policy=empty_security.policy --enable-preview ConstructorPermissionTest
30  */
31 
32 import java.io.ByteArrayInputStream;
33 import java.io.ByteArrayOutputStream;
34 import java.io.IOException;
35 import java.io.InvalidObjectException;
36 import java.io.ObjectInputStream;
37 import java.io.ObjectOutputStream;
38 import java.io.Serializable;
39 import java.net.Socket;
40 import java.nio.file.Files;
41 import java.nio.file.Path;
42 import java.security.AccessControlException;
43 import org.testng.annotations.DataProvider;
44 import org.testng.annotations.Test;
45 import static java.lang.System.out;
46 import static org.testng.Assert.assertTrue;
47 import static org.testng.Assert.expectThrows;
48 
49 /**
50  * Ensures that the appropriate exception, invalid object exception, with a
51  * suitable cause, is thrown when the record constructor performs a privileged
52  * operation without permission.
53  */
54 public class ConstructorPermissionTest {
55 
56     /** "big switch" that can be used to allow/disallow record construction
57      * set to true after the data provider has constructed all record objects */
58     private static volatile boolean firstDataSetCreated;
59 
60     record R1 () implements Serializable {
61         public R1 {
62             if (firstDataSetCreated) {
63                 try { Files.list(Path.of(".")); }
64                 catch (IOException unexpected) { throw new AssertionError(unexpected); }
65             }
66         }
67     }
68 
69     record R2 (int x) implements Serializable {
70         public R2 {
71             if (firstDataSetCreated) {
72                 try { new Socket("localhost", 8080); }
73                 catch (IOException unexpected) { throw new AssertionError(unexpected); }
74             }
75         }
76     }
77 
78     record R3 (String... args) implements Serializable {
79         public R3 {
80             if (firstDataSetCreated)
ProcessHandle.current()81                 ProcessHandle.current();
82         }
83     }
84 
85     static final Class<InvalidObjectException> IOE = InvalidObjectException.class;
86 
87     @DataProvider(name = "exceptionInstances")
exceptionInstances()88     public Object[][] exceptionInstances() {
89         var objs = new Object[][] {
90             new Object[] { new R1(),     AccessControlException.class, "FilePermission"   },
91             new Object[] { new R2(1),    AccessControlException.class, "SocketPermission" },
92             new Object[] { new R3("s"),  AccessControlException.class, "manageProcess"    },
93         };
94         firstDataSetCreated = true;
95         return objs;
96     }
97 
98     @Test(dataProvider = "exceptionInstances")
testExceptions(Object objectToSerialize, Class<? extends Throwable> expectedExType, String expectedExMessage)99     public void testExceptions(Object objectToSerialize,
100                                Class<? extends Throwable> expectedExType,
101                                String expectedExMessage)
102         throws Exception
103     {
104         out.println("\n---");
105         out.println("serializing: " + objectToSerialize);
106         byte[] bytes = serialize(objectToSerialize);
107         InvalidObjectException ioe = expectThrows(IOE, () -> deserialize(bytes));
108         out.println("caught expected IOE: " + ioe);
109         Throwable t = ioe.getCause();
110         assertTrue(t.getClass().equals(expectedExType),
111                    "Expected:" + expectedExType + ", got:" + t);
112         out.println("expected cause " + expectedExType +" : " + t);
113         String msg = t.getMessage();
114         assertTrue(msg.contains(expectedExMessage),
115                    "Expected message to contain [" + expectedExMessage + "], in " + msg);
116     }
117 
118     // TODO: add positive tests with permissions granted.
119 
120     // --- infra
121 
serialize(T obj)122     static <T> byte[] serialize(T obj) throws IOException {
123         ByteArrayOutputStream baos = new ByteArrayOutputStream();
124         ObjectOutputStream oos = new ObjectOutputStream(baos);
125         oos.writeObject(obj);
126         oos.close();
127         return baos.toByteArray();
128     }
129 
130     @SuppressWarnings("unchecked")
deserialize(byte[] streamBytes)131     static <T> T deserialize(byte[] streamBytes)
132         throws IOException, ClassNotFoundException
133     {
134         ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes);
135         ObjectInputStream ois  = new ObjectInputStream(bais);
136         return (T) ois.readObject();
137     }
138 }
139