CVE-2018-6331 (published 2018-12-31T23:29Z)

Buck parser-cache command loads/saves state using Java serialized object. If the state information is maliciously crafted, deserializing it could lead to code execution. This issue affects Buck versions prior to v2018.06.25.01.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-502
cwe_name: Deserialization of Untrusted Data
description: The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
extended_description: 
            It is often convenient to serialize objects for communication or to save them for later use. However, deserialized data or code can often be modified without using the provided accessor functions if it does not use cryptography to protect itself. Furthermore, any cryptography would still be client-side security -- which is a dangerous security assumption.
            Data that is untrusted can not be trusted to be well-formed.
            When developers place no restrictions on "gadget chains," or series of instances and method invocations that can self-execute during the deserialization process (i.e., before the object is returned to the caller), it is sometimes possible for attackers to leverage them to perform unauthorized actions, like generating a shell.
         
         

Fix information:
repo_url: https://github.com/facebook/buck
commit_details: repo_url: https://github.com/facebook/buck
author: Hoa Dinh
author_date: 2018-03-28 11:24:00-07:00
msg: Prevent deserialization of random objects

Summary:
Fixed security issue with buck parser-cache command.
This diff prevent deserialization of random objects.

Reviewed By: jtorkkola

fbshipit-source-id: 24e8221
repository: repo_url: https://github.com/facebook/buck
repo_name: facebook/buck
description: A fast build system that encourages the creation of small, reusable modules over a variety of platforms and languages.
repo_language: Java
owner: facebook
file_changes: filename: ParserCacheCommand.java
change_type: ModificationType.MODIFY
diff: @@ -17,6 +17,7 @@
 package com.facebook.buck.cli;
 
 import com.facebook.buck.parser.ParserConfig;
+import com.facebook.buck.parser.ParserStateObjectInputStream;
 import com.facebook.buck.parser.thrift.RemoteDaemonicParserState;
 import com.facebook.buck.util.ExitCode;
 import com.facebook.buck.util.json.ObjectMappers;
@@ -88,7 +89,7 @@ public class ParserCacheCommand extends AbstractCommand {
           ZipInputStream zipis = new ZipInputStream(fis)) {
         ZipEntry entry = zipis.getNextEntry();
         Preconditions.checkState(entry.getName().equals("parser_data"));
-        try (ObjectInputStream ois = new ObjectInputStream(zipis)) {
+        try (ObjectInputStream ois = new ParserStateObjectInputStream(zipis)) {
           RemoteDaemonicParserState state;
           try {
             state = (RemoteDaemonicParserState) ois.readObject();

programming_language: Java
filename: BUCK
change_type: ModificationType.MODIFY
diff: @@ -35,6 +35,7 @@ java_immutables_library(
         "Parser.java",
         "ParserMessages.java",
         "ParserPythonInterpreterProvider.java",
+        "ParserStateObjectInputStream.java",
         "ParserTargetNodeFactory.java",
         "PerBuildState.java",
         "PipelineNodeCache.java",

programming_language: Shell
filename: ParserStateObjectInputStream.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,48 @@
+/*
+ * Copyright 2013-present Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.facebook.buck.parser;
+
+import com.facebook.buck.parser.thrift.RemoteDaemonicParserState;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InvalidClassException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.util.HashSet;
+import java.util.Set;
+
+/** A ObjectInputStream that will deserialize only RemoteDaemonicParserState. */
+public class ParserStateObjectInputStream extends ObjectInputStream {
+
+  private Set<String> whitelist;
+
+  public ParserStateObjectInputStream(InputStream inputStream) throws IOException {
+    super(inputStream);
+
+    whitelist = new HashSet<>();
+    whitelist.add(RemoteDaemonicParserState.class.getName());
+  }
+
+  @Override
+  protected Class<?> resolveClass(ObjectStreamClass desc)
+      throws IOException, ClassNotFoundException {
+    if (!whitelist.contains(desc.getName())) {
+      throw new InvalidClassException(desc.getName(), "Can't deserialize this class");
+    }
+    return super.resolveClass(desc);
+  }
+}

programming_language: Java
filename: ParserCacheCommandIntegrationTest.java
change_type: ModificationType.MODIFY
diff: @@ -28,17 +28,26 @@ import com.facebook.buck.testutil.integration.TestContext;
 import com.facebook.buck.testutil.integration.TestDataHelper;
 import com.facebook.buck.util.HumanReadableException;
 import com.facebook.buck.util.NamedTemporaryFile;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InvalidClassException;
+import java.io.ObjectOutputStream;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 import org.hamcrest.Matchers;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 public class ParserCacheCommandIntegrationTest {
 
   @Rule public TemporaryPaths tmp = new TemporaryPaths();
+  @Rule public ExpectedException thrown = ExpectedException.none();
 
   @Test
   public void testSaveAndLoad() throws IOException {
@@ -146,4 +155,28 @@ public class ParserCacheCommandIntegrationTest {
           e.getMessage(), Matchers.containsString("//Apps:TestAppsLibrary could not be found"));
     }
   }
+
+  @Test
+  public void testInvalidData() throws IOException {
+    Map<String, String> invalidData = new HashMap();
+    invalidData.put("foo", "bar");
+
+    NamedTemporaryFile tempFile = new NamedTemporaryFile("invalid_parser_data", null);
+    try (FileOutputStream fos = new FileOutputStream(tempFile.get().toString());
+        ZipOutputStream zipos = new ZipOutputStream(fos)) {
+      zipos.putNextEntry(new ZipEntry("parser_data"));
+      try (ObjectOutputStream oos = new ObjectOutputStream(zipos)) {
+        oos.writeObject(invalidData);
+      }
+    }
+
+    ProjectWorkspace workspace =
+        TestDataHelper.createProjectWorkspaceForScenario(this, "parser_with_cell", tmp);
+    workspace.setUp();
+
+    // Load the invalid parser cache data.
+    thrown.expect(InvalidClassException.class);
+    thrown.expectMessage("Can't deserialize this class");
+    workspace.runBuckCommand("parser-cache", "--load", tempFile.get().toString());
+  }
 }

programming_language: Java