Skip to content

Commit b93c91b

Browse files
authored
Merge pull request #2920 from idodeclare/bugfix/symlink_laxness
Bugfix/symlink laxness
2 parents 07aff55 + e230f5d commit b93c91b

25 files changed

+1586
-407
lines changed

opengrok-indexer/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ information: Portions Copyright [yyyy] [name of copyright owner]
1919
CDDL HEADER END
2020
2121
Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved.
22-
Portions Copyright (c) 2017-2018, Chris Fraire <[email protected]>.
22+
Portions Copyright (c) 2017-2019, Chris Fraire <[email protected]>.
2323
2424
-->
2525
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
@@ -51,6 +51,11 @@ Portions Copyright (c) 2017-2018, Chris Fraire <[email protected]>.
5151
<artifactId>commons-lang3</artifactId>
5252
<version>${apache-commons-lang3.version}</version>
5353
</dependency>
54+
<dependency>
55+
<groupId>org.apache.commons</groupId>
56+
<artifactId>commons-compress</artifactId>
57+
<version>1.19</version>
58+
</dependency>
5459
<dependency>
5560
<groupId>org.apache.lucene</groupId>
5661
<artifactId>lucene-core</artifactId>

opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/AnalyzerGuru.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@
112112
import org.opengrok.indexer.history.HistoryReader;
113113
import org.opengrok.indexer.logger.LoggerFactory;
114114
import org.opengrok.indexer.search.QueryBuilder;
115-
import org.opengrok.indexer.util.ForbiddenSymlinkException;
116115
import org.opengrok.indexer.util.IOUtils;
117116
import org.opengrok.indexer.web.Util;
118117

@@ -527,12 +526,10 @@ public static void returnAnalyzers() {
527526
* @param xrefOut Where to write the xref (possibly {@code null})
528527
* @throws IOException If an exception occurs while collecting the data
529528
* @throws InterruptedException if a timeout occurs
530-
* @throws ForbiddenSymlinkException if symbolic-link checking encounters
531-
* an ineligible link
532529
*/
533530
public void populateDocument(Document doc, File file, String path,
534531
AbstractAnalyzer fa, Writer xrefOut) throws IOException,
535-
InterruptedException, ForbiddenSymlinkException {
532+
InterruptedException {
536533

537534
String date = DateTools.timeToString(file.lastModified(),
538535
DateTools.Resolution.MILLISECOND);

opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Configuration.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
/*
2121
* Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
22-
* Portions Copyright (c) 2017-2018, Chris Fraire <[email protected]>.
22+
* Portions Copyright (c) 2017-2019, Chris Fraire <[email protected]>.
2323
*/
2424
package org.opengrok.indexer.configuration;
2525

@@ -201,6 +201,7 @@ public final class Configuration {
201201
private String CTagsExtraOptionsFile;
202202
private int scanningDepth;
203203
private Set<String> allowedSymlinks;
204+
private Set<String> canonicalRoots;
204205
private boolean obfuscatingEMailAddresses;
205206
private boolean chattyStatusPage;
206207
private final Map<String, String> cmds; // repository type -> command
@@ -441,6 +442,7 @@ public Configuration() {
441442
//setBugPage("http://bugs.myserver.org/bugdatabase/view_bug.do?bug_id=");
442443
setBugPattern("\\b([12456789][0-9]{6})\\b");
443444
setCachePages(5);
445+
setCanonicalRoots(new HashSet<>());
444446
setCommandTimeout(600); // 10 minutes
445447
setInteractiveCommandTimeout(30);
446448
setCompressXref(true);
@@ -1173,6 +1175,14 @@ public void setAllowedSymlinks(Set<String> allowedSymlinks) {
11731175
this.allowedSymlinks = allowedSymlinks;
11741176
}
11751177

1178+
public Set<String> getCanonicalRoots() {
1179+
return canonicalRoots;
1180+
}
1181+
1182+
public void setCanonicalRoots(Set<String> canonicalRoots) {
1183+
this.canonicalRoots = canonicalRoots;
1184+
}
1185+
11761186
public boolean isObfuscatingEMailAddresses() {
11771187
return obfuscatingEMailAddresses;
11781188
}

opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/RuntimeEnvironment.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
/*
2121
* Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
22-
* Portions Copyright (c) 2017-2018, Chris Fraire <[email protected]>.
22+
* Portions Copyright (c) 2017-2019, Chris Fraire <[email protected]>.
2323
*/
2424
package org.opengrok.indexer.configuration;
2525

@@ -419,12 +419,32 @@ public void setSourceRoot(String sourceRoot) {
419419
* @return Path relative to source root
420420
* @throws IOException If an IO error occurs
421421
* @throws FileNotFoundException if the file is not relative to source root
422+
* or if {@code sourceRoot} is not defined
422423
* @throws ForbiddenSymlinkException if symbolic-link checking encounters
423424
* an ineligible link
424425
*/
425426
public String getPathRelativeToSourceRoot(File file)
426427
throws IOException, ForbiddenSymlinkException {
427-
return PathUtils.getPathRelativeToSourceRoot(file, 0);
428+
String sourceRoot = getSourceRootPath();
429+
if (sourceRoot == null) {
430+
throw new FileNotFoundException("sourceRoot is not defined");
431+
}
432+
433+
String maybeRelPath = PathUtils.getRelativeToCanonical(file.getPath(),
434+
sourceRoot, getAllowedSymlinks(), getCanonicalRoots());
435+
File maybeRelFile = new File(maybeRelPath);
436+
if (!maybeRelFile.isAbsolute()) {
437+
/*
438+
* N.b. OpenGrok has a weird convention that source-root "relative"
439+
* paths must start with a '/' as they are elsewhere directly
440+
* appended to getSourceRootPath() and also stored as such.
441+
*/
442+
maybeRelPath = File.separator + maybeRelPath;
443+
return maybeRelPath;
444+
}
445+
446+
throw new FileNotFoundException("Failed to resolve [" + file.getPath()
447+
+ "] relative to source root [" + sourceRoot + "]");
428448
}
429449

430450
/**
@@ -1189,6 +1209,15 @@ public void setAllowedSymlinks(Set<String> allowedSymlinks) {
11891209
setConfigurationValue("allowedSymlinks", allowedSymlinks);
11901210
}
11911211

1212+
@SuppressWarnings("unchecked")
1213+
public Set<String> getCanonicalRoots() {
1214+
return (Set<String>) getConfigurationValue("canonicalRoots");
1215+
}
1216+
1217+
public void setCanonicalRoots(Set<String> canonicalRoots) {
1218+
setConfigurationValue("canonicalRoots", canonicalRoots);
1219+
}
1220+
11921221
/**
11931222
* Return whether e-mail addresses should be obfuscated in the xref.
11941223
* @return if we obfuscate emails
@@ -1456,8 +1485,7 @@ private void generateProjectRepositoriesMap() throws IOException {
14561485
Project proj;
14571486
String repoPath;
14581487
try {
1459-
repoPath = PathUtils.getPathRelativeToSourceRoot(
1460-
new File(r.getDirectoryName()), 0);
1488+
repoPath = getPathRelativeToSourceRoot(new File(r.getDirectoryName()));
14611489
} catch (ForbiddenSymlinkException e) {
14621490
LOGGER.log(Level.FINER, e.getMessage());
14631491
continue;

opengrok-indexer/src/main/java/org/opengrok/indexer/history/GitRepository.java

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.io.InputStream;
3131
import java.io.InputStreamReader;
3232
import java.io.Reader;
33+
import java.nio.charset.StandardCharsets;
3334
import java.nio.file.Paths;
3435
import java.text.ParseException;
3536
import java.util.ArrayList;
@@ -38,7 +39,6 @@
3839
import java.util.LinkedList;
3940
import java.util.List;
4041
import java.util.TreeSet;
41-
import java.util.function.Supplier;
4242
import java.util.logging.Level;
4343
import java.util.logging.Logger;
4444
import java.util.regex.Matcher;
@@ -166,10 +166,11 @@ Executor getRenamedFilesExecutor(final File file, String sinceRevision) throws I
166166
cmd.add(sinceRevision + "..");
167167
}
168168

169-
if (file.getCanonicalPath().length() > getDirectoryName().length() + 1) {
169+
String canonicalPath = file.getCanonicalPath();
170+
if (canonicalPath.length() > getCanonicalDirectoryName().length() + 1) {
170171
// this is a file in the repository
171172
cmd.add("--");
172-
cmd.add(file.getCanonicalPath().substring(getDirectoryName().length() + 1));
173+
cmd.add(getPathRelativeToCanonicalRepositoryRoot(canonicalPath));
173174
}
174175

175176
return new Executor(cmd, new File(getDirectoryName()), sinceRevision != null);
@@ -195,9 +196,8 @@ private HistoryRevResult getHistoryRev(
195196
* Be careful, git uses only forward slashes in its command and output (not in file path).
196197
* Using backslashes together with git show will get empty output and 0 status code.
197198
*/
198-
String filename = Paths.get(getDirectoryName()).relativize(Paths.get(fullpath))
199-
.toString()
200-
.replace(File.separatorChar, '/');
199+
String filename = Paths.get(getCanonicalDirectoryName()).relativize(
200+
Paths.get(fullpath)).toString().replace(File.separatorChar, '/');
201201
ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
202202
String[] argv = {
203203
RepoCommand,
@@ -234,13 +234,9 @@ boolean getHistoryGet(
234234
String fullpath;
235235
try {
236236
fullpath = new File(parent, basename).getCanonicalPath();
237-
} catch (IOException exp) {
238-
LOGGER.log(Level.SEVERE, exp, new Supplier<String>() {
239-
@Override
240-
public String get() {
241-
return String.format("Failed to get canonical path: %s/%s", parent, basename);
242-
}
243-
});
237+
} catch (IOException e) {
238+
LOGGER.log(Level.WARNING, e, () -> String.format(
239+
"Failed to get canonical path: %s/%s", parent, basename));
244240
return false;
245241
}
246242

@@ -255,17 +251,21 @@ public String get() {
255251
try {
256252
origpath = findOriginalName(fullpath, rev);
257253
} catch (IOException exp) {
258-
LOGGER.log(Level.SEVERE, exp, new Supplier<String>() {
259-
@Override
260-
public String get() {
261-
return String.format("Failed to get original revision: %s/%s (revision %s)", parent, basename, rev);
262-
}
263-
});
254+
LOGGER.log(Level.SEVERE, exp, () -> String.format(
255+
"Failed to get original revision: %s/%s (revision %s)",
256+
parent, basename, rev));
264257
return false;
265258
}
266259

267260
if (origpath != null) {
268-
final String fullRenamedPath = Paths.get(getDirectoryName(), origpath).toString();
261+
String fullRenamedPath;
262+
try {
263+
fullRenamedPath = Paths.get(getCanonicalDirectoryName(), origpath).toString();
264+
} catch (IOException e) {
265+
LOGGER.log(Level.WARNING, e, () -> String.format(
266+
"Failed to get canonical path: .../%s", origpath));
267+
return false;
268+
}
269269
if (!fullRenamedPath.equals(fullpath)) {
270270
result = getHistoryRev(sink, fullRenamedPath, rev);
271271
}
@@ -281,19 +281,22 @@ public String get() {
281281
*
282282
* @param input a stream with the output from a log or blame command
283283
* @return a reader that reads the input
284-
* @throws IOException if the reader cannot be created
285284
*/
286-
static Reader newLogReader(InputStream input) throws IOException {
285+
static Reader newLogReader(InputStream input) {
287286
// Bug #17731: Git always encodes the log output using UTF-8 (unless
288287
// overridden by i18n.logoutputencoding, but let's assume that hasn't
289288
// been done for now). Create a reader that uses UTF-8 instead of the
290289
// platform's default encoding.
291-
return new InputStreamReader(input, "UTF-8");
290+
return new InputStreamReader(input, StandardCharsets.UTF_8);
292291
}
293292

294-
private String getPathRelativeToRepositoryRoot(String fullpath) {
295-
String repoPath = getDirectoryName() + File.separator;
296-
return fullpath.replace(repoPath, "");
293+
private String getPathRelativeToCanonicalRepositoryRoot(String fullpath)
294+
throws IOException {
295+
String repoPath = getCanonicalDirectoryName() + File.separator;
296+
if (fullpath.startsWith(repoPath)) {
297+
return fullpath.substring(repoPath.length());
298+
}
299+
return fullpath;
297300
}
298301

299302
/**
@@ -304,11 +307,11 @@ private String getPathRelativeToRepositoryRoot(String fullpath) {
304307
* @param changeset changeset
305308
* @return original filename relative to the repository root
306309
* @throws java.io.IOException if I/O exception occurred
307-
* @see #getPathRelativeToRepositoryRoot(String)
310+
* @see #getPathRelativeToCanonicalRepositoryRoot(String)
308311
*/
309-
protected String findOriginalName(String fullpath, String changeset)
312+
String findOriginalName(String fullpath, String changeset)
310313
throws IOException {
311-
if (fullpath == null || fullpath.isEmpty() || fullpath.length() < getDirectoryName().length()) {
314+
if (fullpath == null || fullpath.isEmpty()) {
312315
throw new IOException(String.format("Invalid file path string: %s", fullpath));
313316
}
314317

@@ -317,7 +320,7 @@ protected String findOriginalName(String fullpath, String changeset)
317320
fullpath, changeset));
318321
}
319322

320-
String fileInRepo = getPathRelativeToRepositoryRoot(fullpath);
323+
String fileInRepo = getPathRelativeToCanonicalRepositoryRoot(fullpath);
321324

322325
/*
323326
* Get the list of file renames for given file to the specified
@@ -445,7 +448,7 @@ public Annotation annotate(File file, String revision) throws IOException {
445448
}
446449
}
447450
cmd.add("--");
448-
cmd.add(getPathRelativeToRepositoryRoot(file.getCanonicalPath()));
451+
cmd.add(getPathRelativeToCanonicalRepositoryRoot(file.getCanonicalPath()));
449452

450453
Executor exec = new Executor(cmd, new File(getDirectoryName()),
451454
RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());

opengrok-indexer/src/main/java/org/opengrok/indexer/history/HistoryGuru.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public final class HistoryGuru {
6868
*/
6969
private static final HistoryGuru INSTANCE = new HistoryGuru();
7070

71+
private final RuntimeEnvironment env;
72+
7173
/**
7274
* The history cache to use.
7375
*/
@@ -91,10 +93,10 @@ public final class HistoryGuru {
9193
* control system.
9294
*/
9395
private HistoryGuru() {
94-
HistoryCache cache = null;
95-
RuntimeEnvironment env = RuntimeEnvironment.getInstance();
96+
env = RuntimeEnvironment.getInstance();
9697
scanningDepth = env.getScanningDepth();
9798

99+
HistoryCache cache = null;
98100
if (env.useHistoryCache()) {
99101
cache = new FileHistoryCache();
100102

@@ -244,7 +246,7 @@ public History getHistory(File file, boolean withFiles, boolean ui)
244246
final File dir = file.isDirectory() ? file : file.getParentFile();
245247
final Repository repo = getRepository(dir);
246248

247-
RemoteSCM rscm = RuntimeEnvironment.getInstance().getRemoteScmSupported();
249+
RemoteSCM rscm = env.getRemoteScmSupported();
248250
boolean doRemote = (ui && (rscm == RemoteSCM.UIONLY))
249251
|| (rscm == RemoteSCM.ON)
250252
|| (ui || ((rscm == RemoteSCM.DIRBASED) && (repo != null) && repo.hasHistoryForDirectories()));
@@ -318,9 +320,9 @@ public boolean hasHistory(File file) {
318320

319321
// This should return true for Annotate view.
320322
return repo.isWorking() && repo.fileHasHistory(file)
321-
&& ((RuntimeEnvironment.getInstance().getRemoteScmSupported() == RemoteSCM.ON)
322-
|| (RuntimeEnvironment.getInstance().getRemoteScmSupported() == RemoteSCM.UIONLY)
323-
|| (RuntimeEnvironment.getInstance().getRemoteScmSupported() == RemoteSCM.DIRBASED)
323+
&& ((env.getRemoteScmSupported() == RemoteSCM.ON)
324+
|| (env.getRemoteScmSupported() == RemoteSCM.UIONLY)
325+
|| (env.getRemoteScmSupported() == RemoteSCM.DIRBASED)
324326
|| !repo.isRemote());
325327
}
326328

@@ -417,9 +419,11 @@ private Collection<RepositoryInfo> addRepositories(File[] files,
417419
} catch (IllegalAccessException iae) {
418420
LOGGER.log(Level.WARNING, "Could not create repository for '"
419421
+ file + "', missing access rights.", iae);
422+
continue;
420423
} catch (ForbiddenSymlinkException e) {
421-
LOGGER.log(Level.WARNING, "Could not create repository for '"
422-
+ file + "', path traversal issues.", e);
424+
LOGGER.log(Level.WARNING, "Could not create repository for ''{0}'': {1}",
425+
new Object[] {file, e.getMessage()});
426+
continue;
423427
}
424428
if (repository == null) {
425429
// Not a repository, search its sub-dirs.
@@ -541,8 +545,7 @@ private void createCache(Repository repository, String sinceRevision) {
541545

542546
private void createCacheReal(Collection<Repository> repositories) {
543547
Statistics elapsed = new Statistics();
544-
ExecutorService executor = RuntimeEnvironment.getInstance().
545-
getIndexerParallelizer().getHistoryExecutor();
548+
ExecutorService executor = env.getIndexerParallelizer().getHistoryExecutor();
546549
// Since we know each repository object from the repositories
547550
// collection is unique, we can abuse HashMap to create a list of
548551
// repository,revision tuples with repository as key (as the revision
@@ -704,7 +707,7 @@ public void createCache() {
704707
*/
705708
private List<Repository> getReposFromString(Collection<String> repositories) {
706709
ArrayList<Repository> repos = new ArrayList<>();
707-
File srcRoot = RuntimeEnvironment.getInstance().getSourceRootFile();
710+
File srcRoot = env.getSourceRootFile();
708711

709712
for (String file : repositories) {
710713
File f = new File(srcRoot, file);

opengrok-indexer/src/main/java/org/opengrok/indexer/history/RepositoryFactory.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,16 @@ public static Repository getRepository(File file, boolean interactive)
123123
public static Repository getRepository(File file, boolean interactive, boolean isNested)
124124
throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException,
125125
IOException, ForbiddenSymlinkException {
126+
126127
RuntimeEnvironment env = RuntimeEnvironment.getInstance();
127-
Repository repo = null;
128+
String relFile = env.getPathRelativeToSourceRoot(file);
128129

130+
Repository repo = null;
129131
for (Repository rep : repositories) {
130132
if ((!isNested || rep.isNestable()) && rep.isRepositoryFor(file, interactive)) {
131133
repo = rep.getClass().getDeclaredConstructor().newInstance();
132134

133-
if (env.isProjectsEnabled() && env.getPathRelativeToSourceRoot(file).equals(File.separator)) {
135+
if (env.isProjectsEnabled() && relFile.equals(File.separator)) {
134136
LOGGER.log(Level.WARNING, "{0} was detected as {1} repository however with directory " +
135137
"matching source root. This is invalid because projects are enabled. Ignoring this " +
136138
"repository.",

0 commit comments

Comments
 (0)