Skip to content

Commit

Permalink
create parent dir on fileio:write operation (qzind#920)
Browse files Browse the repository at this point in the history
* creates parent dir on fileio:write
* adds properties: security.file.enabled, security.file.strict

Co-authored-by: Tres Finocchiaro <[email protected]>
  • Loading branch information
Vzor- and tresf authored Mar 25, 2022
1 parent f2258be commit 1123c78
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 12 deletions.
3 changes: 3 additions & 0 deletions src/qz/common/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public class Constants {
public static final String PREFS_IDLE_PRINTERS = "tray.idle.printers";
public static final String PREFS_IDLE_JFX = "tray.idle.javafx";

public static final String PREFS_FILEIO_ENABLED = "security.file.enabled";
public static final String PREFS_FILEIO_STRICT = "security.file.strict";

public static final String ALLOW_SITES_TEXT = "Permanently allowed \"%s\" to access local resources";
public static final String BLOCK_SITES_TEXT = "Permanently blocked \"%s\" from accessing local resources";

Expand Down
8 changes: 6 additions & 2 deletions src/qz/common/TrayManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,17 @@ public TrayManager(boolean isHeadless) {
// Set strict certificate mode preference
Certificate.setTrustBuiltIn(!getPref(Constants.PREFS_STRICT_MODE, false));

//headless if turned on by user or unsupported by environment
// Set FileIO security
FileUtilities.setFileIoEnabled(getPref(Constants.PREFS_FILEIO_ENABLED, true));
FileUtilities.setFileIoStrict(getPref(Constants.PREFS_FILEIO_STRICT, false));

// Headless if turned on by user or unsupported by environment
headless = isHeadless || getPref(Constants.PREFS_HEADLESS, false) || GraphicsEnvironment.isHeadless();
if (headless) {
log.info("Running in headless mode");
}

// Setup the shortcut name so that the UI components can use it
// Set up the shortcut name so that the UI components can use it
shortcutCreator = ShortcutCreator.getInstance();

SystemUtilities.setSystemLookAndFeel();
Expand Down
61 changes: 52 additions & 9 deletions src/qz/utils/FileUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -217,21 +217,64 @@ public static Path inheritParentPermissions(Path filePath) {
private static HashMap<String,File> localFileMap = new HashMap<>();
private static HashMap<String,File> sharedFileMap = new HashMap<>();
private static ArrayList<Map.Entry<Path,String>> whiteList;
private static boolean FILE_IO_ENABLED = true;
private static boolean FILE_IO_STRICT = false;

public static void setFileIoEnabled(boolean enabled) {
FILE_IO_ENABLED = enabled;
}

public static void setFileIoStrict(boolean strict) {
FILE_IO_STRICT = strict;
}

/**
* Performs security checks before allowing File IO operations:
* 1. Is the request verified (was the signature OK?)?
* 2. Is the certificate valid?
* 3. Is the location whitelisted?
* 4. Is the file extension permitted
*/
private static void checkFileRequest(Path path, FileParams fp, RequestState request, boolean allowRootDir) throws AccessDeniedException {
if(!FILE_IO_ENABLED) {
throw new AccessDeniedException("File operations are disabled");
} else if(!request.isVerified() && FILE_IO_STRICT) {
throw new AccessDeniedException("File requests is not verified");
} else if(request.getCertUsed() == null || !request.getCertUsed().isTrusted()) {
throw new AccessDeniedException("Certificate provided is not trusted");
} else if(!isWhiteListed(path, allowRootDir, fp.isSandbox(), request)) {
throw new AccessDeniedException("File operation is not in a permitted location");
} else if(!allowRootDir && !Files.isDirectory(path)) {
if (!isGoodExtension(path)) {
throw new AccessDeniedException(path.toString());
}
}
}

public static Path getAbsolutePath(JSONObject params, RequestState request, boolean allowRootDir) throws JSONException, IOException {
return getAbsolutePath(params, request, allowRootDir, false);
}

public static Path getAbsolutePath(JSONObject params, RequestState request, boolean allowRootDir, boolean createMissing) throws JSONException, IOException {
FileParams fp = new FileParams(params);
String commonName = request.isVerified()? escapeFileName(request.getCertName()):"UNTRUSTED";

Path path = createAbsolutePath(fp, commonName);
checkFileRequest(path, fp, request, allowRootDir);
initializeRootFolder(fp, commonName);

if (!isWhiteListed(path, allowRootDir, fp.isSandbox(), request.getCertUsed())) {
throw new AccessDeniedException(path.toString());
}

if (!allowRootDir && !Files.isDirectory(path)) {
if (!isGoodExtension(path)) {
throw new AccessDeniedException(path.toString());
if (createMissing) {
if (!SystemUtilities.isWindows()) {
Path resolve;
// Find existing parental directory
for(resolve = path.getParent(); !Files.exists(resolve); resolve = resolve.getParent()) {
// do nothing
}
Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(resolve);
FileAttribute<Set<PosixFilePermission>> fileAttributes = PosixFilePermissions.asFileAttribute(permissions);
Files.createDirectories(path.getParent(), fileAttributes);
} else {
Files.createDirectories(path.getParent());
}
}

Expand Down Expand Up @@ -309,8 +352,8 @@ public static boolean isGoodExtension(Path path) {
* Currently hard-coded to the QZ data directory or anything provided by qz-tray.properties
* e.g. %APPDATA%/qz/data or $HOME/.qz/data, etc
*/
public static boolean isWhiteListed(Path path, boolean allowRootDir, boolean sandbox, Certificate cert) {
String commonName = cert.isTrusted()? escapeFileName(cert.getCommonName()):"UNTRUSTED";
public static boolean isWhiteListed(Path path, boolean allowRootDir, boolean sandbox, RequestState request) {
String commonName = request.isVerified()? escapeFileName(request.getCertName()):"UNTRUSTED";
if (whiteList == null) {
whiteList = new ArrayList<>();
//default sandbox locations. More can be added through the properties file
Expand Down
2 changes: 1 addition & 1 deletion src/qz/ws/PrintSocketClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ private void processMessage(Session session, JSONObject json, SocketConnection c
}
case FILE_WRITE: {
FileParams fileParams = new FileParams(params);
Path absPath = FileUtilities.getAbsolutePath(params, request, false);
Path absPath = FileUtilities.getAbsolutePath(params, request, false, true);

Files.write(absPath, fileParams.getData(), StandardOpenOption.CREATE, fileParams.getAppendMode());
FileUtilities.inheritParentPermissions(absPath);
Expand Down

0 comments on commit 1123c78

Please sign in to comment.