Skip to content

Restrict parent directory traversal for finding the associated project #251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
patches/7654.diff
patches/7670.diff
patches/7690.diff
patches/7722.diff
patches/7733.diff
patches/mvn-sh.diff
patches/generate-dependencies.diff
Expand Down
193 changes: 193 additions & 0 deletions patches/7722.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
diff --git a/extide/gradle/src/org/netbeans/modules/gradle/ProjectTrust.java b/extide/gradle/src/org/netbeans/modules/gradle/ProjectTrust.java
index 1d7ad89a8714..216291f86fd4 100644
--- a/extide/gradle/src/org/netbeans/modules/gradle/ProjectTrust.java
+++ b/extide/gradle/src/org/netbeans/modules/gradle/ProjectTrust.java
@@ -25,10 +25,10 @@
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
-import java.util.Random;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -69,7 +69,7 @@ public class ProjectTrust {
byte[] buf = prefs.getByteArray(KEY_SALT, null);
if (buf == null) {
buf = new byte[16];
- new Random().nextBytes(buf);
+ new SecureRandom().nextBytes(buf);
prefs.putByteArray(KEY_SALT, buf);
}
salt = buf;
@@ -134,7 +134,7 @@ public void trustProject(Project project, boolean permanently) {
if (permanently && !isTrustedPermanently(project)) {
Path trustFile = getProjectTrustFile(project);
byte[] rnd = new byte[16];
- new Random().nextBytes(rnd);
+ new SecureRandom().nextBytes(rnd);
String projectId = toHex(rnd);
projectTrust.put(pathId, projectId);
try {
diff --git a/ide/projectapi/arch.xml b/ide/projectapi/arch.xml
index e91502b7570b..81349ae3f7d0 100644
--- a/ide/projectapi/arch.xml
+++ b/ide/projectapi/arch.xml
@@ -509,8 +509,9 @@ Nothing.
<p>
<api name="project.limitScanRoot" category="friend" group="systemproperty" type="export">
<p>
- If defined, limits search for a parent project to a certain subtree. The property defines <b>absolute path</b> of a folder
- where upwards search for a project in parent folders is terminated. Queries outside of the root will not find any project.
+ If defined, limits search for a parent project to a certain subtree. The property defines the <b>absolute path</b> of a folder
+ where upwards search for a project in parent folders is terminated. Queries outside the root will not find any project.
+ Multiple folders may be specified when delimited with OS-specific path separators (':' on *nix, ';' on Windows).
Currently used for tests so the tested runtime does not escape the workdir.
</p>
</api>
diff --git a/ide/projectapi/src/org/netbeans/modules/projectapi/SimpleFileOwnerQueryImplementation.java b/ide/projectapi/src/org/netbeans/modules/projectapi/SimpleFileOwnerQueryImplementation.java
index 05b887129a52..bf4b435f4dbf 100644
--- a/ide/projectapi/src/org/netbeans/modules/projectapi/SimpleFileOwnerQueryImplementation.java
+++ b/ide/projectapi/src/org/netbeans/modules/projectapi/SimpleFileOwnerQueryImplementation.java
@@ -19,6 +19,7 @@

package org.netbeans.modules.projectapi;

+import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
@@ -27,10 +28,9 @@
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -45,6 +45,7 @@
import org.netbeans.api.project.ProjectManager;
import org.netbeans.spi.project.FileOwnerQueryImplementation;
import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
import org.openide.filesystems.URLMapper;
import org.openide.util.BaseUtilities;
import org.openide.util.NbPreferences;
@@ -59,21 +60,19 @@ public class SimpleFileOwnerQueryImplementation implements FileOwnerQueryImpleme
private static final Logger LOG = Logger.getLogger(SimpleFileOwnerQueryImplementation.class.getName());
private static final URI UNOWNED_URI = URI.create("http:unowned");
private static final Set<String> forbiddenFolders;
- private static final String projectScanRoot;
+ private static final Set<String> projectScanRoots;

static {
- Set<String> files = new HashSet<String>();
- String root = null;
+ Set<String> folders = null;
+ Set<String> roots = null;
try {
- root = System.getProperty("project.limitScanRoot"); // NOI18N
- String forbidden = System.getProperty("project.forbiddenFolders", System.getProperty("versioning.forbiddenFolders", "")); //NOI18N
- files.addAll(Arrays.asList(forbidden.split("\\;"))); //NOI18N
- files.remove(""); //NOI18N
+ roots = separatePaths(System.getProperty("project.limitScanRoot"), File.pathSeparator); //NOI18N
+ folders = separatePaths(System.getProperty("project.forbiddenFolders", System.getProperty("versioning.forbiddenFolders")), ";"); //NOI18N
} catch (Exception e) {
LOG.log(Level.INFO, e.getMessage(), e);
}
- forbiddenFolders = files;
- projectScanRoot = root;
+ forbiddenFolders = folders == null ? Collections.emptySet() : folders;
+ projectScanRoots = roots;
}

/** Do nothing */
@@ -113,7 +112,7 @@ public Project getOwner(FileObject f) {

deserialize();
while (f != null) {
- if (projectScanRoot != null && !f.getPath().startsWith(projectScanRoot)) {
+ if (projectScanRoots != null && projectScanRoots.stream().noneMatch(f.getPath()::startsWith)) {
break;
}
boolean folder = f.isFolder();
@@ -137,8 +136,8 @@ public Project getOwner(FileObject f) {
}
folders.add(f);
if (!forbiddenFolders.contains(f.getPath()) &&
- !hasRoot(externalOwners.keySet(), f, folder, furi) &&
- !hasRoot(deserializedExternalOwners.keySet(), f, folder, furi)) {
+ !hasRoot(externalOwners.keySet(), f, true, furi) &&
+ !hasRoot(deserializedExternalOwners.keySet(), f, true, furi)) {
Project p;
try {
p = ProjectManager.getDefault().findProject(f);
@@ -414,6 +413,40 @@ private static URI goUp(URI u) {
assert u.toString().startsWith(nue.toString()) : "not a parent: " + nue + " of " + u;
return nue;
}
+
+ private static Set<String> separatePaths(String joinedPaths, String pathSeparator) {
+ if (joinedPaths == null || joinedPaths.isEmpty())
+ return null;
+
+ Set<String> paths = null;
+ for (String split : joinedPaths.split(pathSeparator)) {
+ if ((split = split.trim()).isEmpty()) continue;
+
+ // Ensure that variations in terms of ".." or "." or windows drive-letter case differences are removed.
+ // File.getCanonicalFile() will additionally resolve symlinks, which is not required.
+ File file = FileUtil.normalizeFile(new File(split));
+
+ // Store FileObject.getPath(); because getOwner() compares these with FileObject.getPath() strings.
+ // This has some peculiarities as compared to File.getAbsolutePath(); such as return "" for File("/").
+ FileObject fileObject = FileUtil.toFileObject(file);
+ // This conversion may get rid of non-existent paths.
+ if (fileObject == null) continue;
+
+ String path = fileObject.getPath();
+ if (path == null || path.isEmpty()) continue;
+
+ if (paths == null) {
+ paths = Collections.singleton(path); // more performant in usage when only a single element is present.
+ } else {
+ if (paths.size() == 1) {
+ paths = new LinkedHashSet<>(paths); // more performant in iteration
+ }
+ paths.add(path);
+ }
+ }
+ return paths;
+ }
+
private static final boolean WINDOWS = BaseUtilities.isWindows();

}
diff --git a/platform/o.n.bootstrap/src/org/netbeans/CLIHandler.java b/platform/o.n.bootstrap/src/org/netbeans/CLIHandler.java
index 633ee72340de..3140aba8eafa 100644
--- a/platform/o.n.bootstrap/src/org/netbeans/CLIHandler.java
+++ b/platform/o.n.bootstrap/src/org/netbeans/CLIHandler.java
@@ -45,7 +45,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openide.util.RequestProcessor;
@@ -580,7 +579,7 @@ static Status initialize(
enterState(10, block);

final byte[] arr = new byte[KEY_LENGTH];
- new Random().nextBytes(arr);
+ new SecureRandom().nextBytes(arr);


final RandomAccessFile os = raf;
5 changes: 5 additions & 0 deletions vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@
"type": "boolean",
"default": false,
"description": "%jdk.configuration.disableNbJavac.description%"
},
"jdk.advanced.disable.projectSearchLimit": {
"type": "boolean",
"default": false,
"description": "%jdk.configuration.disableProjectSearchLimit.description%"
}
}
},
Expand Down
1 change: 1 addition & 0 deletions vscode/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"jdk.configuration.runConfig.env.description": "Environment variables",
"jdk.configuration.runConfig.cwd.description": "Working directory",
"jdk.configuration.disableNbJavac.description": "Advanced option: disable nb-javac library, javac from the selected JDK will be used. The selected JDK must be at least JDK 22.",
"jdk.configuration.disableProjectSearchLimit.description": "Advanced option: disable limits on searching in containing folders for project information.",
"jdk.debugger.configuration.mainClass.description": "Absolute path to the program main class.",
"jdk.debugger.configuration.classPaths.description": "The classpaths for launching the JVM.",
"jdk.debugger.configuration.console.description": "The specified console to launch the program.",
Expand Down
29 changes: 28 additions & 1 deletion vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import { InputStep, MultiStepInput } from './utils';
import { PropertiesView } from './propertiesView/propertiesView';
import { openJDKSelectionView } from './jdkDownloader';
import { l10n } from './localiser';
import { ORACLE_VSCODE_EXTENSION_ID,NODE_WINDOWS_LABEL } from './constants';
import { ORACLE_VSCODE_EXTENSION_ID, NODE_WINDOWS_LABEL } from './constants';
const API_VERSION : string = "1.0";
const SERVER_NAME : string = "Oracle Java SE Language Server";
export const COMMAND_PREFIX : string = "jdk";
Expand Down Expand Up @@ -918,11 +918,38 @@ function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContex
enableModules.push('org.netbeans.libs.nbjavacapi');
}

let projectSearchRoots:string = '';
const isProjectFolderSearchLimited : boolean = !netbeansConfig.get('advanced.disable.projectSearchLimit', false);
if (isProjectFolderSearchLimited) {
try {
projectSearchRoots = os.homedir() as string;
} catch (err:any) {
handleLog(log, `Failed to obtain the user home directory due to: ${err}`);
}
if (!projectSearchRoots) {
projectSearchRoots = os.type() === NODE_WINDOWS_LABEL ? '%USERPROFILE%' : '$HOME'; // The launcher script may perform the env variable substitution
handleLog(log, `Using userHomeDir = "${projectSearchRoots}" as the launcher script may perform env var substitution to get its value.`);
}
const workspaces = workspace.workspaceFolders;
if (workspaces) {
workspaces.forEach(workspace => {
if (workspace.uri) {
try {
projectSearchRoots = projectSearchRoots + path.delimiter + path.normalize(workspace.uri.fsPath);
} catch (err:any) {
handleLog(log, `Failed to get the workspace path: ${err}`);
}
}
});
}
}

let info = {
clusters : findClusters(context.extensionPath),
extensionPath: context.extensionPath,
storagePath : userdir,
jdkHome : specifiedJDK,
projectSearchRoots: projectSearchRoots,
verbose: beVerbose,
disableModules : disableModules,
enableModules : enableModules,
Expand Down
6 changes: 6 additions & 0 deletions vscode/src/nbcode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface LaunchInfo {
extensionPath: string;
storagePath: string;
jdkHome: string | unknown;
projectSearchRoots? : string;
verbose? : boolean;
enableModules? : string[];
disableModules? : string[];
Expand Down Expand Up @@ -102,6 +103,11 @@ export function launch(
if (info.jdkHome) {
ideArgs.push('--jdkhome', info.jdkHome as string);
}

if (info.projectSearchRoots) {
ideArgs.push(`-J-Dproject.limitScanRoot="${info.projectSearchRoots}"`);
}

if (info.verbose) {
ideArgs.push('-J-Dnetbeans.logger.console=true');
}
Expand Down