Repository: redpen-cc/redpen
Pull request #528 (2015-12-31 14:35:33)

load dictionary files and JavaScript valdiators only from directories under $REDPEN_HOME for the security reasons
load dictionary files and JavaScript valdiators only from directories under $REDPEN_HOME for the security reasons

Allowing clients to specify any file can be vulnerable.
This fix will lower the risk.



Patch:
diff --git a/.gitignore b/.gitignore
index 86e8ca78..7db52902 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,4 +35,7 @@ com_crashlytics_export_strings.xml
 target
 
 # misc
-*~
\ No newline at end of file
+*~
+
+# Maven
+*.releaseBackup
diff --git a/pom.xml b/pom.xml
index 1d192ac3..528cbf6a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     <modelVersion>4.0.0</modelVersion>
 
     <groupId>cc.redpen</groupId>
-    <version>1.4.3-SNAPSHOT</version>
+    <version>1.4.4-SNAPSHOT</version>
     <artifactId>redpen</artifactId>
     <url>http://redpen.cc</url>
     <name>redpen</name>
@@ -58,6 +58,18 @@
                     <consoleOutput>true</consoleOutput>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-release-plugin</artifactId>
+                <version>2.4.2</version>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.maven.scm</groupId>
+                        <artifactId>maven-scm-provider-gitexe</artifactId>
+                        <version>1.9</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/redpen-cli/pom.xml b/redpen-cli/pom.xml
index 64ad6856..e4c26929 100644
--- a/redpen-cli/pom.xml
+++ b/redpen-cli/pom.xml
@@ -5,11 +5,11 @@
     <parent>
         <groupId>cc.redpen</groupId>
         <artifactId>redpen</artifactId>
-        <version>1.4.3-SNAPSHOT</version>
+        <version>1.4.4-SNAPSHOT</version>
     </parent>
 
     <artifactId>redpen-cli</artifactId>
-    <version>1.4.3-SNAPSHOT</version>
+    <version>1.4.4-SNAPSHOT</version>
     <name>redpen-cli</name>
     <description>RedPen is an open source proofreading tool a tool to check if your technical documents meet the writing standard . RedPen supports various markup text formats (Markdown, Textile, AsciiDoc, and LaTeX).</description>
     <url>http://redpen.cc</url>
@@ -106,12 +106,12 @@
         <dependency>
             <groupId>cc.redpen</groupId>
             <artifactId>redpen-core</artifactId>
-            <version>1.4.3-SNAPSHOT</version>
+            <version>1.4.4-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>cc.redpen</groupId>
             <artifactId>redpen-plugin</artifactId>
-            <version>1.4.3-SNAPSHOT</version>
+            <version>1.4.4-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/redpen-core/pom.xml b/redpen-core/pom.xml
index c03e7772..f70e5ce0 100644
--- a/redpen-core/pom.xml
+++ b/redpen-core/pom.xml
@@ -5,11 +5,11 @@
     <parent>
         <groupId>cc.redpen</groupId>
         <artifactId>redpen</artifactId>
-        <version>1.4.3-SNAPSHOT</version>
+        <version>1.4.4-SNAPSHOT</version>
     </parent>
 
     <artifactId>redpen-core</artifactId>
-    <version>1.4.3-SNAPSHOT</version>
+    <version>1.4.4-SNAPSHOT</version>
     <name>redpen-core</name>
     <description>RedPen is an open source proofreading tool a tool to check if your technical documents meet the writing standard . RedPen supports various markup text formats (Markdown, Textile, AsciiDoc, and LaTeX).</description>
     <url>http://redpen.cc</url>
diff --git a/redpen-core/src/main/java/cc/redpen/RedPen.java b/redpen-core/src/main/java/cc/redpen/RedPen.java
index 63411dfe..3b6f567f 100644
--- a/redpen-core/src/main/java/cc/redpen/RedPen.java
+++ b/redpen-core/src/main/java/cc/redpen/RedPen.java
@@ -46,7 +46,7 @@ import java.util.Map;
 public class RedPen {
     private static final Logger LOG = LoggerFactory.getLogger(RedPen.class);
 
-    public static final String VERSION = "1.4.1";
+    public static final String VERSION = "1.4.3";
 
     private final Configuration configuration;
     private final SentenceExtractor sentenceExtractor;
diff --git a/redpen-core/src/main/java/cc/redpen/util/DictionaryLoader.java b/redpen-core/src/main/java/cc/redpen/util/DictionaryLoader.java
index d8baebba..85f5eefa 100644
--- a/redpen-core/src/main/java/cc/redpen/util/DictionaryLoader.java
+++ b/redpen-core/src/main/java/cc/redpen/util/DictionaryLoader.java
@@ -23,7 +23,8 @@ import org.slf4j.LoggerFactory;
 
 import java.io.*;
 import java.nio.charset.StandardCharsets;
-import java.util.*;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.function.BiConsumer;
 import java.util.function.Supplier;
 
@@ -121,6 +122,7 @@ public final class DictionaryLoader<E> {
      */
     public E loadCachedFromFile(File file, String dictionaryName) throws RedPenException {
         String path = file.getAbsolutePath();
+        ensureFileIsInsideRedPenHomeOrWorkingDirectory(path);
         if (!file.exists()) {
             throw new RedPenException("File not found: " + file);
         }
@@ -152,4 +154,35 @@ public final class DictionaryLoader<E> {
         LOG.info("Succeeded to load " + dictionaryName + ".");
         return loaded;
     }
+
+    /**
+     * Test the specified path is inside $REDPEN_HOME, or JVM working directory. If not, throw RedPenException
+     *
+     * @param path path to test
+     * @throws RedPenException the specified path is not inside $REDPEN_HOME
+     */
+    public static void ensureFileIsInsideRedPenHomeOrWorkingDirectory(String path) throws RedPenException {
+            try {
+                String canonicalPath = new File(path).getCanonicalPath();
+                String currentDirectory = new File("").getCanonicalPath();
+                if (canonicalPath.startsWith(currentDirectory)) {
+                    return;
+                }
+                String home = System.getProperty("REDPEN_HOME", System.getenv("REDPEN_HOME"));
+                String homeCanonicalPath;
+                if (home != null) {
+                    homeCanonicalPath = new File(home).getCanonicalPath();
+                    if (canonicalPath.startsWith(homeCanonicalPath)) {
+                        return;
+                    }
+                }else{
+                    homeCanonicalPath = "not specified";
+                }
+                throw new RedPenException(String.format("%s  is not under $REDPEN_HOME(%s) or current directory(%s).",
+                        canonicalPath , homeCanonicalPath, currentDirectory));
+
+            } catch (IOException e) {
+                throw new RedPenException(e);
+            }
+    }
 }
diff --git a/redpen-core/src/main/java/cc/redpen/validator/JavaScriptValidator.java b/redpen-core/src/main/java/cc/redpen/validator/JavaScriptValidator.java
index 216dfdf2..de6f3e59 100644
--- a/redpen-core/src/main/java/cc/redpen/validator/JavaScriptValidator.java
+++ b/redpen-core/src/main/java/cc/redpen/validator/JavaScriptValidator.java
@@ -22,6 +22,7 @@ import cc.redpen.model.Document;
 import cc.redpen.model.Section;
 import cc.redpen.model.Sentence;
 import cc.redpen.tokenizer.TokenElement;
+import cc.redpen.util.DictionaryLoader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -62,13 +63,16 @@ public class JavaScriptValidator extends Validator {
 
     @Override
     protected void init() throws RedPenException {
-        String jsValidatorsPath = getConfigAttribute("script-path").orElse(
-                System.getenv("REDPEN_HOME") + File.separator + DEFAULT_JS_VALIDATORS_PATH);
+        String home = System.getProperty("REDPEN_HOME", System.getenv("REDPEN_HOME"));
+        home = home != null ? home : ".";
+
+        String jsValidatorsPath = getConfigAttribute("script-path").orElse(home + File.separator + DEFAULT_JS_VALIDATORS_PATH);
         File jsDirectory = new File(jsValidatorsPath);
         if(!jsDirectory.exists()){
-            LOG.info("JavaScript validators directory is missing: {}", jsDirectory.getAbsolutePath());
+            LOG.info("JavaScript validators directory is missing: {}", jsValidatorsPath);
         }else {
-            LOG.info("JavaScript validators directory: {}", jsDirectory.getAbsolutePath());
+            LOG.info("JavaScript validators directory: {}", jsValidatorsPath);
+            DictionaryLoader.ensureFileIsInsideRedPenHomeOrWorkingDirectory(jsValidatorsPath);
             File[] jsValidatorFiles = jsDirectory.listFiles();
             if (jsValidatorFiles != null) {
                 for (File file : jsValidatorFiles) {
diff --git a/redpen-distribution/pom.xml b/redpen-distribution/pom.xml
index da2bac49..764a4cdb 100644
--- a/redpen-distribution/pom.xml
+++ b/redpen-distribution/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>redpen</artifactId>
         <groupId>cc.redpen</groupId>
-        <version>1.4.3-SNAPSHOT</version>
+        <version>1.4.4-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/redpen-plugin/pom.xml b/redpen-plugin/pom.xml
index 21ec1b60..0ad25f23 100644
--- a/redpen-plugin/pom.xml
+++ b/redpen-plugin/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>redpen</artifactId>
         <groupId>cc.redpen</groupId>
-        <version>1.4.3-SNAPSHOT</version>
+        <version>1.4.4-SNAPSHOT</version>
     </parent>
     <packaging>jar</packaging>
     <modelVersion>4.0.0</modelVersion>
@@ -57,7 +57,7 @@
         <dependency>
             <groupId>cc.redpen</groupId>
             <artifactId>redpen-core</artifactId>
-            <version>1.4.3-SNAPSHOT</version>
+            <version>1.4.4-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>junit</groupId>
diff --git a/redpen-server/pom.xml b/redpen-server/pom.xml
index b1415ec3..b7cc3ec0 100644
--- a/redpen-server/pom.xml
+++ b/redpen-server/pom.xml
@@ -3,7 +3,7 @@
     <parent>
         <artifactId>redpen</artifactId>
         <groupId>cc.redpen</groupId>
-        <version>1.4.3-SNAPSHOT</version>
+        <version>1.4.4-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -165,7 +165,7 @@
         <dependency>
             <groupId>cc.redpen</groupId>
             <artifactId>redpen-core</artifactId>
-            <version>1.4.3-SNAPSHOT</version>
+            <version>1.4.4-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.apache.wink</groupId>


Test patch:
diff --git a/redpen-core/src/test/java/cc/redpen/util/DictionaryLoaderTest.java b/redpen-core/src/test/java/cc/redpen/util/DictionaryLoaderTest.java
index 26f8f188..60251967 100644
--- a/redpen-core/src/test/java/cc/redpen/util/DictionaryLoaderTest.java
+++ b/redpen-core/src/test/java/cc/redpen/util/DictionaryLoaderTest.java
@@ -31,6 +31,7 @@ import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 public class DictionaryLoaderTest extends Validator {
     @Test
@@ -75,6 +76,7 @@ public class DictionaryLoaderTest extends Validator {
     public void testLoadCachedFile() throws IOException, RedPenException {
         Path path = Files.createTempFile("test", "txt");
         File file = path.toFile();
+        System.setProperty("REDPEN_HOME", file.getParentFile().getAbsolutePath());
         Files.copy(new ByteArrayInputStream("foo".getBytes()), path, StandardCopyOption.REPLACE_EXISTING);
         Set<String> strings;
         strings = WORD_LIST.loadCachedFromFile(path.toFile(), "temp file");
@@ -103,4 +105,19 @@ public class DictionaryLoaderTest extends Validator {
         assertTrue(strings.contains("foo"));
         assertTrue(strings.contains("bar"));
     }
+
+    @Test
+    public void testEnsureFileIsInsideRedPenHomeOrWorkingDirectory() throws RedPenException, IOException {
+        File tempFile = File.createTempFile("redpenTest", "redpenTest");
+        String path = tempFile.getAbsolutePath();
+        System.setProperty("REDPEN_HOME", path);
+        DictionaryLoader.ensureFileIsInsideRedPenHomeOrWorkingDirectory(path + File.separator + "test");
+        DictionaryLoader.ensureFileIsInsideRedPenHomeOrWorkingDirectory(new File("fileInCurrentDirectory.txt").getCanonicalPath());
+        try {
+            File file = new File(path + File.separator + ".." + File.separator + "test");
+            DictionaryLoader.ensureFileIsInsideRedPenHomeOrWorkingDirectory(file.getCanonicalPath());
+            fail("expecting RedPenException");
+        } catch (RedPenException expected) {
+        }
+    }
 }
diff --git a/redpen-core/src/test/java/cc/redpen/validator/JavaScriptValidatorTest.java b/redpen-core/src/test/java/cc/redpen/validator/JavaScriptValidatorTest.java
index 9a30ed76..c2003f6a 100644
--- a/redpen-core/src/test/java/cc/redpen/validator/JavaScriptValidatorTest.java
+++ b/redpen-core/src/test/java/cc/redpen/validator/JavaScriptValidatorTest.java
@@ -62,6 +62,7 @@ public class JavaScriptValidatorTest extends JavaScriptValidator {
         // delete the temporary file, make a directory, and store JavaScript validator in it
         javaScriptValidatorsDir.delete();
         javaScriptValidatorsDir.mkdirs();
+        System.setProperty("REDPEN_HOME",javaScriptValidatorsDir.getAbsolutePath());
         File validatorJS = new File(javaScriptValidatorsDir.getAbsolutePath() + File.separator + "MyValidator.js");
         String content2 = "function validateSentence(sentence) {\n" +
                 "addLocalizedError(sentence, 'validation error in JavaScript Validator');}";
diff --git a/redpen-core/src/test/java/cc/redpen/validator/sentence/KatakanaEndHyphenValidatorTest.java b/redpen-core/src/test/java/cc/redpen/validator/sentence/KatakanaEndHyphenValidatorTest.java
index dff982f5..c4255a56 100644
--- a/redpen-core/src/test/java/cc/redpen/validator/sentence/KatakanaEndHyphenValidatorTest.java
+++ b/redpen-core/src/test/java/cc/redpen/validator/sentence/KatakanaEndHyphenValidatorTest.java
@@ -26,7 +26,8 @@ import cc.redpen.RedPenException;
 import cc.redpen.config.Configuration;
 import cc.redpen.config.ValidatorConfiguration;
 import cc.redpen.model.Document;
-import cc.redpen.model.Sentence;
+
+import java.io.File;
 import java.util.Map;
 
 import java.util.ArrayList;
@@ -211,6 +212,7 @@ public class KatakanaEndHyphenValidatorTest {
                         .addSentence(new Sentence("コーヒーと紅茶と、どちらがお好きですか。", 1))
                         .build());
 
+        System.setProperty("REDPEN_HOME",new File("src/test/resources/").getAbsolutePath());
         RedPen redPen = new RedPen(config);
         Map<Document, List<ValidationError>> errors = redPen.validate(documents);
         assertEquals(0, errors.get(documents.get(0)).size());