1 /* 2 * Copyright (c) 2016, 2019, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jfr.internal; 27 28 import java.io.BufferedWriter; 29 import java.io.FileNotFoundException; 30 import java.io.IOException; 31 import java.nio.file.Files; 32 import java.nio.file.Path; 33 import java.security.AccessControlContext; 34 import java.security.AccessController; 35 import java.security.PrivilegedExceptionAction; 36 import java.util.concurrent.Callable; 37 38 /** 39 * Purpose of this class is to simplify analysis of security risks. 40 * <p> 41 * Paths in the public API should be wrapped in this class so we 42 * at all time know what kind of paths we are dealing with. 43 * <p> 44 * A user supplied path must never be used in an unsafe context, such as a 45 * shutdown hook or any other thread created by JFR. 46 * <p> 47 * All operation using this path must happen in {@link #doPriviligedIO(Callable)} 48 */ 49 public final class WriteableUserPath { 50 private final AccessControlContext controlContext; 51 private final Path original; 52 private final Path real; 53 private final String text; 54 55 // Not to ensure security, but to help 56 // against programming errors 57 private volatile boolean inPrivileged; 58 59 public WriteableUserPath(Path path) throws IOException { 60 controlContext = AccessController.getContext(); 61 // verify that the path is writeable 62 if (Files.exists(path) && !Files.isWritable(path)) { 63 // throw same type of exception as FileOutputStream 64 // constructor, if file can't be opened. 65 throw new FileNotFoundException("Could not write to file: " + path.toAbsolutePath()); 66 } 67 // will throw if non-writeable 68 BufferedWriter fw = Files.newBufferedWriter(path); 69 fw.close(); 70 this.original = path; 71 this.real = path.toRealPath(); 72 this.text = real.toString(); 73 } 74 75 /** 76 * Returns a potentially malicious path where the user may have implemented 77 * their own version of Path. This method should never be called in an 78 * unsafe context and the Path value should never be passed along to other 79 * methods. 80 * 81 * @return path from a potentially malicious user 82 */ 83 public Path getPotentiallyMaliciousOriginal() { 84 return original; 85 } 86 87 /** 88 * Returns a string representation of the path. 89 * 90 * @return path as text 91 */ 92 public String getText() { 93 return text; 94 } 95 96 /** 97 * Returns a potentially malicious path where the user may have implemented 98 * their own version of Path. This method should never be called in an 99 * unsafe context and the Path value should never be passed along to other 100 * methods. 101 * 102 * @return path from a potentially malicious user 103 */ 104 public Path getReal() { 105 if (!inPrivileged) { 106 throw new InternalError("A user path was accessed outside the context it was supplied in"); 107 } 108 return real; 109 } 110 111 public void doPriviligedIO(Callable<?> function) throws IOException { 112 try { 113 inPrivileged = true; 114 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { 115 @Override 116 public Void run() throws Exception { 117 function.call(); 118 return null; 119 } 120 }, controlContext); 121 } catch (Throwable t) { 122 // prevent malicious user to propagate exception callback 123 // in the wrong context 124 throw new IOException("Unexpected error during I/O operation"); 125 } finally { 126 inPrivileged = false; 127 } 128 } 129 }