From 10a1454ce0e0b97f00e2792942f1e2131bc877db Mon Sep 17 00:00:00 2001
From: Duarte Meneses <duarte.meneses@sonarsource.com>
Date: Wed, 18 Sep 2019 10:52:44 -0500
Subject: SSF-92 Code injection from SonarQube plugins

---
 .../java/org/sonar/process/SecurityManagement.java | 65 ++++++++++++++++++++
 .../org/sonar/process/SecurityManagementTest.java  | 70 ++++++++++++++++++++++
 2 files changed, 135 insertions(+)
 create mode 100644 server/sonar-process/src/main/java/org/sonar/process/SecurityManagement.java
 create mode 100644 server/sonar-process/src/test/java/org/sonar/process/SecurityManagementTest.java

(limited to 'server/sonar-process')

diff --git a/server/sonar-process/src/main/java/org/sonar/process/SecurityManagement.java b/server/sonar-process/src/main/java/org/sonar/process/SecurityManagement.java
new file mode 100644
index 00000000000..e8f49e0d6d8
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/SecurityManagement.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.process;
+
+import java.lang.reflect.ReflectPermission;
+import java.security.Permission;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.security.SecurityPermission;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SecurityManagement {
+  private SecurityManagement() {
+    // static only
+  }
+
+  public static void restrictPlugins() {
+    SecurityManager sm = new SecurityManager();
+    Policy.setPolicy(new CustomPolicy());
+    System.setSecurityManager(sm);
+  }
+
+  static class CustomPolicy extends Policy {
+    private static final Set<String> ALLOWED_RUNTIME_PERMISSIONS = new HashSet<>(Arrays.asList("getFileSystemAttributes", "readFileDescriptor", "writeFileDescriptor",
+      "getStackTrace", "setDefaultUncaughtExceptionHandler", "manageProcess", "localeServiceProvider", "LoggerFinder"));
+
+    @Override
+    public boolean implies(ProtectionDomain domain, Permission permission) {
+      // classloader used to load plugins
+      String clName = getDomainClassLoaderName(domain);
+      if ("org.sonar.classloader.ClassRealm".equals(clName)) {
+        if (permission instanceof RuntimePermission && !ALLOWED_RUNTIME_PERMISSIONS.contains(permission.getName())) {
+          return false;
+        }
+        if (permission instanceof ReflectPermission || permission instanceof SecurityPermission) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    String getDomainClassLoaderName(ProtectionDomain domain) {
+      return domain.getClassLoader().getClass().getName();
+    }
+  }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/SecurityManagementTest.java b/server/sonar-process/src/test/java/org/sonar/process/SecurityManagementTest.java
new file mode 100644
index 00000000000..afe142b4ea6
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/SecurityManagementTest.java
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.process;
+
+import java.lang.reflect.ReflectPermission;
+import java.security.Permission;
+import java.security.ProtectionDomain;
+import java.security.SecurityPermission;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+
+public class SecurityManagementTest {
+  private ClassLoader classRealm = mock(ClassLoader.class, RETURNS_DEEP_STUBS);
+  private ProtectionDomain pd = new ProtectionDomain(null, null, classRealm, null);
+
+  private Permission allowedRuntime = new RuntimePermission("getFileSystemAttributes");
+  private Permission deniedRuntime = new RuntimePermission("getClassLoader");
+  private Permission reflect = new ReflectPermission("suppressAccessChecks");
+  private Permission security = new SecurityPermission("setPolicy");
+
+  @Test
+  public void policy_restricts_class_realm() {
+    SecurityManagement.CustomPolicy policy = new SecurityManagement.CustomPolicy() {
+      @Override
+      String getDomainClassLoaderName(ProtectionDomain domain) {
+        return "org.sonar.classloader.ClassRealm";
+      }
+    };
+
+    assertThat(policy.implies(pd, allowedRuntime)).isTrue();
+    assertThat(policy.implies(pd, deniedRuntime)).isFalse();
+    assertThat(policy.implies(pd, reflect)).isFalse();
+    assertThat(policy.implies(pd, security)).isFalse();
+  }
+
+  @Test
+  public void policy_does_not_restrict_other_classloaders() {
+    SecurityManagement.CustomPolicy policy = new SecurityManagement.CustomPolicy() {
+      @Override
+      String getDomainClassLoaderName(ProtectionDomain domain) {
+        return "classloader";
+      }
+    };
+
+    assertThat(policy.implies(pd, allowedRuntime)).isTrue();
+    assertThat(policy.implies(pd, deniedRuntime)).isTrue();
+    assertThat(policy.implies(pd, reflect)).isTrue();
+    assertThat(policy.implies(pd, security)).isTrue();
+  }
+}
-- 
cgit v1.2.3