google / guava

Google core libraries for Java
Apache License 2.0
50.2k stars 10.91k forks source link

CVE-2020-8908: Files::createTempDir local information disclosure vulnerability #4011

Closed JLLeitschuh closed 1 year ago

JLLeitschuh commented 4 years ago

IMPORTANT NOTE

Updating to Guava 30.0 does not fix this security vulnerability. The method is merely deprecated. There currently exits no fix for this vulnerability.

https://github.com/google/guava/issues/4011#issuecomment-765672282


Since the fix for this vulnerability is now disclosed by this commit (https://github.com/google/guava/commit/fec0dbc4634006a6162cfd4d0d09c962073ddf40) and it was closed internally by google as 'Intended Functionality' I figure I'll disclose the vulnerability fully.

Vulnerability

File guavaTempDir = com.google.common.io.Files.createTempDir();
System.out.println("Guava Temp Dir: " + guavaTempDir.getName());
runLS(guavaTempDir.getParentFile(), guavaTempDir); // Prints the file permissions -> drwxr-xr-x
File child = new File(guavaTempDir, "guava-child.txt");
child.createNewFile();
runLS(guavaTempDir, child); // Prints the file permissions -> -rw-r--r--

On the flip side, when using java.nio.file.Files, this creates a directory with the correct file permissions.

Path temp = Files.createTempDirectory("random-directory");
System.out.println("Files Temp Dir: " + temp.getFileName());
runLS(temp.toFile().getParentFile(), temp.toFile()); // Prints the file permissions -> drwx------
Path child = temp.resolve("jdk-child.txt");
child.toFile().createNewFile();
runLS(temp.toFile(), child.toFile()); // Prints the file permissions -> -rw-r--r--

Impact

The impact of this vulnerability is that, the file permissions on the file created by com.google.common.io.Files.createTempDir allows an attacker running a malicious program co-resident on the same machine can steal secrets stored in this directory. This is because by default on unix-like operating systems the /temp directory is shared between all users, so if the correct file permissions aren't set by the directory/file creator, the file becomes readable by all other users on that system.

Workaround

This vulnerability can be fixed by explicitly setting the java.io.tmpdir system property to a "safe" directory when starting the JVM.

Resolution

The resolution by the Google team was the following:

The team decided to document the behavior, as well as deprecate the method as other alternatives exist.

This completely makes sense to me, and I think is appropriate. The open question that exists in my mind is whether or not this issue warrants a CVE number issued.

melloware commented 4 years ago

Yuck this code is being reported as a security vulnerability by Sonatype IQ Server as SONATYPE-2020-0926.

RECOMMENDATION There is no non-vulnerable upgrade path for this component/package. We recommend investigating alternative components or replacing all usages of the deprecated Files.createTempDir() method with a safer alternative, such as java.nio.file.Files.createTempDirectory() for Java 7+.

I know its deprecated by why not just rip this code out?

It will be forever reported as a vulnerability by apps like OWASP and Sonatype.

JLLeitschuh commented 4 years ago

Can someone from Google make an official statement on whether or not this issue will receive a CVE number or not?

melloware commented 4 years ago

And leaving the deprecated there is OK so people have Javadoc on alternatives but shouldn't it throw UnsupportedOperationException instead of just leaving this vulnerable code in here for someone to use?

JLLeitschuh commented 4 years ago

@melloware, this is indeed a security vulnerability, however, given that the severity of this vulnerability is quite low, I think that the way that Google and the Guava team has handled this vulnerability with a documented depreciation is appropriate.

It's important to note that the JDK actually contains a method with a very similar vulnerability. You'll notice that the documentation on File.createTempFile method changed between Java 6 and Java 7.

This new line of documentation reads:

The Files.createTempFile method provides an alternative method to create an empty file in the temporary-file directory. Files created by that method may have more restrictive access permissions to files created by this method and so may be more suited to security-sensitive applications.

melloware commented 4 years ago

I understand but its listed in Sonatype with a score of 7 which means my management team freaks out we are using a JAR with a vulnerability. Even if you prove to management you aren't using that feature it doesn't mean someone tomorrow in the org couldn't accidentally start using it. So that is why I am PRO on removal vs documentation.

When you work at a very security sensitive client who receives daily reports that include this type of alert.

image

JLLeitschuh commented 4 years ago

A few options for you.

  1. Push back on Sonatype's analysis of this vulnerability. As a customer, if you think that your security vendor's analysis of a given vulnerability is rating the vuln as too high, then push back. Feel free to include me in the email thread. My email address can be found on my GitHub profile.
  2. Push back on management. Here's an ArchUnit test that you can add into your JUnit or TestNG tests that will verify that this method isn't used anywhere, thus preventing its from being used by someone tomorrow.

https://github.com/TNG/ArchUnit

@ArchTest
public static final ArchRule forbid_calls_to_guava_Files_createTempDir =
    classes()
        .should(not(callMethod(com.google.common.io.Files.class, "createTempDir")))
        .because("Files::createTempDir contains a local information disclosure vulnerability (https://github.com/google/guava/issues/4011)");
melloware commented 4 years ago

Cool I didn't know about ArchUnit!!! It won't help much we have over 50 in production applications using it so we would have to add this test to each one and all future applications.

As for your email when I click on your profile GitHub gives me the "Unicorn page is taking too long to load!"??? you must have a lot in your profile.

JLLeitschuh commented 4 years ago

It won't help much we have over 50 in production applications using it so we would have to add this test to each one and all future applications.

Agreed.

A few suggestions for you:

  1. Create one Gradle build that pulls down all of the JAR artifacts from your different applications, load them all onto the classpath of that Gradle build, and run the check there. Sounds complicated, but it may not be if you publish all your jars to the same internal Sonatype instance.
  2. Something that may scale better is GitHub's Code Scanning feature. I'm a OSS Security researcher that contributes to the GitHub Security Lab Bug Bounty program. Your question here actually inspired me to finally write the queries below for my GitHub Security Lab Bug Bounty submission.

The following two CodeQL queries would find all instances of this vulnerability across your codebases.

TempDirsUtil.qll (This is a utility the two queries below depend upon)

import java
import semmle.code.java.dataflow.FlowSources

class MethodAccessSystemGetProperty extends MethodAccess {
  MethodAccessSystemGetProperty() {
    getMethod() instanceof MethodSystemGetProperty
  }

  predicate hasPropertyName(string propertyName) {
    this.getArgument(0).(CompileTimeConstantExpr).getStringValue() = propertyName
  }
}

class MethodAccessSystemGetPropertyTempDir extends MethodAccessSystemGetProperty {
  MethodAccessSystemGetPropertyTempDir() { this.hasPropertyName("java.io.tmpdir") }
}

/**
 * Find dataflow from the temp directory system property to the `File` constructor.
 * Examples:
 *  - `new File(System.getProperty("java.io.tmpdir"))`
 *  - `new File(new File(System.getProperty("java.io.tmpdir")), "/child")`
 */
private predicate isTaintedFileCreation(Expr expSource, Expr exprDest) {
  exists(ConstructorCall construtorCall |
    construtorCall.getConstructedType() instanceof TypeFile and
    construtorCall.getArgument(0) = expSource and
    construtorCall = exprDest
  )
}

private class TaintFollowingFileMethod extends Method {
  TaintFollowingFileMethod() {
    getDeclaringType() instanceof TypeFile and
    (
      hasName("getAbsoluteFile") or
      hasName("getCanonicalFile")
    )
  }
}

private predicate isTaintFollowingFileTransformation(Expr expSource, Expr exprDest) {
  exists(MethodAccess fileMethodAccess |
    fileMethodAccess.getMethod() instanceof TaintFollowingFileMethod and
    fileMethodAccess.getQualifier() = expSource and
    fileMethodAccess = exprDest
  )
}

predicate isAdditionalFileTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
  isTaintedFileCreation(node1.asExpr(), node2.asExpr()) or
  isTaintFollowingFileTransformation(node1.asExpr(), node2.asExpr())
}

Query 1

Finds

/**
 * @name Temporary Directory Local information disclosure
 * @description Detect local information disclosure via the java temporary directory
 * @kind problem
 * @problem.severity warning
 * @precision very-high
 * @id java/local-information-disclosure
 * @tags security
 *       external/cwe/cwe-200
 */

import TempDirUtils

/**
 * All `java.io.File::createTempFile` methods.
 */
class MethodFileCreateTempFile extends Method {
  MethodFileCreateTempFile() {
    this.getDeclaringType() instanceof TypeFile and
    this.hasName("createTempFile")
  }
}

class TempDirSystemGetPropertyToAnyConfig extends TaintTracking::Configuration {
  TempDirSystemGetPropertyToAnyConfig() { this = "TempDirSystemGetPropertyToAnyConfig" }

  override predicate isSource(DataFlow::Node source) {
    source.asExpr() instanceof MethodAccessSystemGetPropertyTempDir
  }

  override predicate isSink(DataFlow::Node source) { any() }

  override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
    isAdditionalFileTaintStep(node1, node2)
  }
}

abstract class MethodAccessInsecureFileCreation extends MethodAccess { }

/**
 * Insecure calls to `java.io.File::createTempFile`.
 */
class MethodAccessInsecureFileCreateTempFile extends MethodAccessInsecureFileCreation {
  MethodAccessInsecureFileCreateTempFile() {
    this.getMethod() instanceof MethodFileCreateTempFile and
    (
      this.getNumArgument() = 2 or
      getArgument(2) instanceof NullLiteral or
      // There exists a flow from the 'java.io.tmpdir' system property to this argument
      exists(TempDirSystemGetPropertyToAnyConfig config |
        config.hasFlowTo(DataFlow::exprNode(getArgument(2)))
      )
    )
  }
}

class MethodGuavaFilesCreateTempFile extends Method {
  MethodGuavaFilesCreateTempFile() {
    getDeclaringType().hasQualifiedName("com.google.common.io", "Files") and
    hasName("createTempDir")
  }
}

class MethodAccessInsecureGuavaFilesCreateTempFile extends MethodAccessInsecureFileCreation {
  MethodAccessInsecureGuavaFilesCreateTempFile() {
    getMethod() instanceof MethodGuavaFilesCreateTempFile
  }
}
from MethodAccessInsecureFileCreation methodAccess
select methodAccess,
  "Local information disclosure vulnerability due to use of file or directory readable by other local users."

Example of this query finding vulns against other Google projects: https://lgtm.com/query/7917272935407723538/

The above query will find method calls like this:

File.createTempFile("biz", "baz", null); // Flagged vulnerable
File.createTempFile("biz", "baz"); // Flagged vulnerable
com.google.common.io.Files.createTempDir(); // Flagged vulnerable
File tempDirChild = new File(new File(System.getProperty("java.io.tmpdir")), "/child"); // Not Flagged
File.createTempFile("random", "file", tempDirChild); // Flagged vulnerable

Query 2:

/**
 * @name Temporary Directory Local information disclosure
 * @description Detect local information disclosure via the java temporary directory
 * @kind path-problem
 * @problem.severity warning
 * @precision very-high
 * @id java/local-information-disclosure
 * @tags security
 *       external/cwe/cwe-200
 */

import TempDirUtils
import DataFlow::PathGraph

private class MethodFileSystemCreation extends Method {
  MethodFileSystemCreation() {
    getDeclaringType() instanceof TypeFile and
    (
      hasName("mkdir") or
      hasName("createNewFile")
    )
  }
}

private class TempDirSystemGetPropertyToCreateConfig extends TaintTracking::Configuration {
  TempDirSystemGetPropertyToCreateConfig() { this = "TempDirSystemGetPropertyToAnyConfig" }

  override predicate isSource(DataFlow::Node source) {
    source.asExpr() instanceof MethodAccessSystemGetPropertyTempDir
  }

  override predicate isSink(DataFlow::Node sink) {
    exists (MethodAccess ma |
      ma.getMethod() instanceof MethodFileSystemCreation and
      ma.getQualifier() = sink.asExpr()
    )
  }

  override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
    isAdditionalFileTaintStep(node1, node2)
  }
}

from DataFlow::PathNode source, DataFlow::PathNode sink, TempDirSystemGetPropertyToCreateConfig conf
where conf.hasFlowPath(source, sink)
select source.getNode(), source, sink,
  "Local information disclosure vulnerability from $@ due to use of file or directory readable by other local users.", source.getNode(),
  "system temp directory"

Example of this query finding vulns against other Google projects: https://lgtm.com/query/548722881855915017/

The above query will find instances of this vulnerability by doing dataflow analysis to find where uses of the system property flow to a file creation location.

File tempDirChild = new File(System.getProperty("java.io.tmpdir"), "/child"); // Not flagged
tempDirChild.mkdir(); // Flagged vulnerable
tempDirChild.createNewFile(); // Flagged vulnerable

With the GitHub Code Scanning feature, once my queries are merged, you'll automatically get alerts about these vulnerabilities in your code. The pull request can be found here: https://github.com/github/codeql/pull/4388


As for your email when I click on your profile GitHub gives me the "Unicorn page is taking too long to load!"??? you must have a lot in your profile.

You sometimes have to reload a few times. I have over 1,596 forks against my profile. I have a bot that I use to automate the generation of thousands of security-fix pull requests across GitHub projects. The first project I did this for, I forked all the projects under my personal account, I have since learned this is a mistake πŸ˜† . Legit, do not do this. GitHub doesn't scale well to having this many forks bound to your account. The second project, where I generated 3,880 pull requests, I realized that it was better for the health of my account if I forked them under organizations instead. I ended up creating 45 GitHub organizations for that project.

melloware commented 4 years ago

@JLLeitschuh Than you for all your work! That GitHub bot code will be a huge help!

semmalimayan commented 4 years ago

@JLLeitschuh does the com.google.common.io.Files.createParentDirs() creates with similar vulnerable permissions ?

JLLeitschuh commented 4 years ago

@semmalimayan here's the method you're asking about.

https://github.com/google/guava/blob/fec0dbc4634006a6162cfd4d0d09c962073ddf40/guava/src/com/google/common/io/Files.java#L470-L487

The true answer here is "it depends".

For example, this would be vulnerable:

File tempDirChild = new File(System.getProperty("java.io.tmpdir"), "/child/text_file.txt");
com.google.common.io.Files.createParentDirs(tempDirChild); // Anything in the 'child' directory would have a local information disclosure vulnerability 
tempDirChild.createNewFile(); // This file would be visible to other users, putting sensitive information in this file would be an information disclosure vulnerability.

That being said, I think that at that point, this is probably more likely a user-error vulnerability, less a guava vulnerability.

JLLeitschuh commented 4 years ago

A similar vulnerability has been disclosed and patched in JUnit 4. CVE pending.

https://github.com/junit-team/junit4/security/advisories/GHSA-269g-pwp5-87pp

To reiterate my earlier question:

Can someone from Google make an official statement on whether or not this issue will receive a CVE number or not?

CC: @google-admin

melloware commented 4 years ago

Here is the fix Junit did: https://github.com/junit-team/junit4/commit/610155b8c22138329f0723eec22521627dbc52ae#diff-25d902c24283ab8cfbac54dfa101ad31

melloware commented 3 years ago

@JLLeitschuh I submitted a PR for this so we will see. I feel like its totally harmless since the method is deprecated anyway.

JLLeitschuh commented 3 years ago

After spending some time talking with some others in the security community, defaulting to having a CVE number assigned is prefered. It will help keep the ecosystem sane by providing one common identifier between all organizations (Sonatype, Snyk, ect...)

With respect to: https://github.com/google/guava/pull/5324

Can someone from Google make an official statement on whether or not this issue will receive a CVE number or not?

I'd prefer if Google did the CVE issuance for this vulnerability, but since I haven't gotten an official statement about it, I'll just go to the parent CNA, MITRE, to request it.

melloware commented 3 years ago

Once you get a CVE number please come back and edit this Ticket title to add the CVE number.

JLLeitschuh commented 3 years ago

Stating that I was going to file a formal appeal to MITRE finally got action on behalf of Google. They have assigned CVE-2020-8908 which should be disclosed later this week.

tony-- commented 3 years ago

The last sentence in the CVE description says, "We recommend updating Guava to version 30.0 or later, or update to Java 7 or later, or to explicitly change the permissions after the creation of the directory if neither are possible."

The ", or update to Java 7 or later," clause suggests that this vulnerability only affects Java < 7, but that's not true, is it? That is, the problem is that Guava < 30.0 uses the old, vulnerable API from Java 6. Updating to Java 7 won't fix this, so that clause should be removed from the recommendation, shouldn't it?

@JLLeitschuh will you comment, please?

JLLeitschuh commented 3 years ago

@tony-- you are correct. Updating to Guava 30.0 won't fix this either. All that 30.0 changes is marking the method as deprecated. #5324 is a fix for it, but it's not been merged yet.

tony-- commented 3 years ago

@JLLeitschuh thanks for responding. Any suggestion for how to communicate the need to update the CVE? I didn't realize it was SO WRONG.

JLLeitschuh commented 3 years ago

I've sent the following message to the Google security team.

Hi Google Team,

The CVE description states the following: https://nvd.nist.gov/vuln/detail/CVE-2020-8908

We recommend updating Guava to version 30.0 or later, or update to Java 7 or later, or to explicitly change the permissions after the creation of the directory if neither are possible.

Neither the first two of these actually fixes the issue. The Google Guava team haven't actually fixed the issue, they've just deprecated the vulnerable method. Updating to Java 7 also doesn't fix the vulnerability, unless you also change your method call.

This is clearly causing confusion for your users: https://github.com/google/guava/issues/4011#issuecomment-765672282

I just want to put this out there. This whole interaction with the Google security team as well as the Guava developers on this issue has been really unpleasant. Multiple times I've asked for follow up without any responses. I feel like I'm herding cats here. I'm bothered that you didn't run the CVE description by me before publishing it. This whole interaction has come across as very disorganized. Frankly, I'm unimpressed.

Combine that with the fact that this vulnerability was determined to not be bountyable, and I'm left with kinda a sour taste in my mouth over this whole thing. I feel like I'm doing a bunch of heavy lifting communicating with your users about this vulnerability, and the only bit of official communication by Google on this issue wasn't even factually correct.

This makes me hesitant to even try to report issues in your OSS via this channel in the future. If you'd be willing to offer some suggestions here, I'd appreciate it. Otherwise, I'm happy to just go the full-disclosure route via the GitHub issue tracker for future OSS security vulnerabilities I find in your software components.

tony-- commented 3 years ago

Any reply that you can share @JLLeitschuh ?

nick-someone commented 3 years ago

Hi @JLLeitschuh!

Nick from the Guava team here. I sincerely apologize for my radio silence on the matter. I take full responsibility for not having been well engaged in this process. I believe my inaction was driven from a sense of avoiding stepping on the security team's toes, or inadvertently causing more consternation by doing the wrong thing (saying something I shouldn't have, not understanding the nature of how to respond to Sonatype vulnerability notices, etc.).

I understand now that my inaction was precisely the wrong thing to have done here. Had I at least coordinated with the security team sooner and more deeply, we could have gotten a better CVE description, and I would have been more likely to answer questions and engage in this bug more fully.

I want to thank you, sincerely, for your detailed reproduction case, and your support in this bug. You've done a great service to users of Guava by helping them resolve their issues, and I also want to thank you for finding similar information disclosure vulnerabilities in other places (see "Similar Vulnerabilities" in https://github.com/junit-team/junit4/security/advisories/GHSA-269g-pwp5-87pp).

Currently, I and the security team are in the process of drafting a new CVE description that more accurately describes the issue and remediation options, which we will also send to you for review before publishing.

Additionally, you may be happy to know that Google's security team is working on a comprehensive security response process for Google's open source projects (like Guava). Once we've got the details sorted out, they will be visible in this repository's /SECURITY and similar places. This security response process is designed, among other things, to enable open source team members to take a more active role in collaborating directly with security researchers and resolving issues that are raised, which should hopefully avoid this type of negative experience in the future.

We really hope that you continue to actively contribute these issues in the future. Again, I apologize sincerely for not having responded well to this helpful contribution, and I really do appreciate your contributions here and to other projects.

Thanks.

JLLeitschuh commented 3 years ago

Wow, this is more than I expected out of this. Thank you for the kind, respectful, and very human apology here. I really honestly do appreciate it.

Additionally, you may be happy to know that Google's security team is working on a comprehensive security response process for Google's open source projects (like Guava).

Please do consider utilizing GitHub Security advisories as a part of this process. I know they aren't perfect yet, but I continue, as a GitHub star, to utilize my contacts inside of GitHub to improve their shortcomings. Although they do still have shortcomings, I still do believe, personally, that they are the best way to do OSS security vulnerability disclosure with GitHub.

Please also considering having an email contact. The reality is, even though this requires monitoring more channels, you're more likely to get a better disclosure the more comfortable the researcher is with the method/manner of disclosure. Be that email, or GitHub.

Currently, I and the security team are in the process of drafting a new CVE description that more accurately describes the issue and remediation options, which we will also send to you for review before publishing.

Awesome. I'm not sure how the CVE update/change notification system operates, but please do try to make sure the downstream advisories generated from that CVE are accurately updated.

Even better, patching the vulnerability would be good, even though the method is deprecated.

In summary, your message is very much appreciated. I'm hopeful to see these future changes. I have no doubt that I'll probably end up having to interface through them, for some Google project, sometime in the near future πŸ˜†.

JLLeitschuh commented 3 years ago

Quick update to everyone following this. I added a "workaround" section to the header of this document. I realized that was missing. Sorry about that.

This vulnerability can be fixed by explicitly setting the java.io.tmpdir system property to a "safe" directory when starting the JVM.

cpovirk commented 3 years ago

Trying to tie together various internal and external discussions over these past months:

@nick-someone , @cgdecker , anything I missed?

JLLeitschuh commented 3 years ago

And if anything, I would suspect that our method's behavior is more likely to cause end users trouble on Android (where other apps can see the files, IIUC) than elsewhere (typically servers, where fewer potentially dangerous processes have access).

Hold up now, 🀯 this actually puts a whole new spin on my research project if this is true. Android apps hostile towards each other is a way more compelling security perspective on this whole issue.

That being said, in all my previous research (ie. googling random terms), I have been completely unable to track down any kind of documentation regarding the behaviours of the java.io.tmpdir in the context of android applications. Some of the documentation I read seemed to indicate that each android app gets its own protected temporary directory. But that has not been confirmed anywhere I've been able to find. @cpovirk, could you do me a favor and ask the android team directly about this issue?

Also, if there is public documentation I can point to regarding this issue, that would also be greatly appreciated. πŸ™‚

cpovirk commented 3 years ago

One thing for us to think more about: While I feel pretty safe autoreplacing Google's usage of this method on the server with nio (at least after some earlier testing), we should figure out how easily Android users can migrate to getCacheDir. Challenges:

cpovirk commented 3 years ago

Eek, I hope I did not just say something completely false :) I will see what I can dig up about java.io.tmpdir on Android.

cpovirk commented 3 years ago

[The Android docs for System.getProperties()](https://developer.android.com/reference/java/lang/System#getProperties()) claim that java.io.tmpdir is set to /sdcard. Presumably that is a shared directory. I suppose it's conceivable that files there magically are restricted to the creating app, but that... would not be my first guess.

However, I came across a comment internally that suggested that the default changed between Ice Cream Sandwich and Jellybean. Now it's supposedly the current app's cache directory. I haven't done all the archaeology on that yet, but I see a commit that looks related in that it copes with changes to java.io.tmpdir over time.

Still, Ice Cream Sandwich devices are out there. And in fact we still run Guava's tests against Ice Cream Sandwich. I wanted to include actual numbers from Google's Distribution Dashboard (to whatever degree we think that's representative of the real world), but it no longer includes them, referring us to "Android Studio's Create New Project wizard" for the information. So let's go with https://9to5google.com/2020/04/10/google-kills-android-distribution-numbers-web/ or https://www.xda-developers.com/android-version-distribution-statistics-android-studio/ instead. Each gives 0.2% ICS almost a year ago. So maybe we don't need to lose too much sleep over Android at this point.

[edit: I subsequently heard that you can download some distribution numbers in JSON format. However, the numbers there are probably an exact match to the ones in the pages I linked above. Notably, I see Ice Cream Sandwich at .2%, and I see no Android 11 at all! It sounds like the numbers may need to be updated manually, and presumably they haven't been.]

cpovirk commented 3 years ago

However, I came across a comment internally that suggested that the default changed between Ice Cream Sandwich and Jellybean. Now it's supposedly the current app's cache directory. I haven't done all the archaeology on that yet

Most likely https://android.googlesource.com/platform/frameworks/base/+/e1d330a071a4574040e6f1147800b4b2c8864043

That made it into the first Jelly Bean release but did not make it into the last Ice Cream Sandwich release. (Tag names are from Google's list.)

JLLeitschuh commented 3 years ago

Any chance you can externalize those links? All of them point to internal Google resources that I can't see as a layperson 😒 πŸ˜†

cpovirk commented 3 years ago

Argh, sorry. Hmm. I will see what I can find. Until I do, here are some more still-internal links: [edit: Update: All links should now go to pages that are publicly visible.]

Here's something that's still not a user-visible doc but is at least a code comment to further back up the idea that that change is intentional: https://android.googlesource.com/platform/libcore/+/56f48d7d7c69b49d35ebf122877f58528a59d76c/ojluni/src/main/java/java/lang/System.java#1034

I also looked at a little at the docs of System.getProperties(). I see the reference to having java.io.tmpdir be /sdcard inserted in 2016, but that appears to just be restoring docs that existed before the switch to OpenJDK sources (which, if not for the per-app override, probably would have set the value to /tmp, whatever that would mean on Android). So no one consciously added that reference at that date.

The doc was originally added in 2010. It could have been true then, but I don't know. I found a commit from 2011 that set the value to /tmp in Java. But it's apparently replacing older native code that I don't intend to try to find, so I don't know if the value in the native code was /tmp, /sdcard, or something else. (I'd guess /tmp, since the commit message doesn't mention changing the value.) Maybe the value changed between 2010 and 2011.

[edit: Still, I wouldn't necessarily assume that the value became /tmp immediately with that commit in 2011. It's possible, but as I note below, the native code hung around a while, and I wouldn't guarantee that the Java code took precedence over it.]

cpovirk commented 3 years ago

OK, I believe that all links now point to pages I can see in an incognito window.

cpovirk commented 3 years ago

One other tiny comment about the Ice Cream Sandwich days: I saw another comment internally that suggested that /sdcard is/was not writable by many apps. (This may even have been part of the reason for changing where java.io.tmpdir points.) This sounds like it could remove the security hole: If no one can write temporary files, then no one can read sensitive data from them! But I suspect that what the comment actually meant is just that apps had to request permission to touch the SD card. And users probably would grant permission, and then we're back to having the problem for any such apps.

cpovirk commented 3 years ago

The /sdcard value in native code dates back to at least Cupcake. (I wasn't going to look, but I stumbled across it in one of the tabs I still had open. If someone wants to investigate more than that, be my guest :))

It presumably [edit: eh, I don't know if I'd put it even that strongly without digging further] became ineffective as of the 2011 override in Java code (linked above), but it hung around until 2014. (edit: See also the original removal before it was accidentally temporarily restored.)

JLLeitschuh commented 3 years ago

[The Android docs for System.getProperties()](https://developer.android.com/reference/java/lang/System#getProperties()) claim that java.io.tmpdir is set to /sdcard. Presumably that is a shared directory. I suppose it's conceivable that files there magically are restricted to the creating app, but that... would not be my first guess.

I think our best bet here is to go with the documented behaviour and assume that that is correct? Or is that being to hopeful about the documentation being up-to-date?

cpovirk commented 3 years ago

I am pretty confident that /sdcard is wrong nowadays. But I think it is well past time for me to do what you originally said and talk to actual Android people.

[Update: I did send a message, but it was already weekend for that team. I'll pull in another person from another team if I don't hear anything in the next few business days.]

prbprbprb commented 3 years ago

FYI

  1. The third column in the Android getProperties() docs is headed "Example" rather than say "Expected", but agree this is misleading and should be changed.
  2. The only place (aside from tests) in AOSP where java.io.tmpdir gets set is the same code Chris referenced. It's a slightly surprising place for it, but at least it gets done. A lot of tests set it to /data/local/tmp which is world writable but I doubt any real apps would do that.
  3. Verified (2) via a toy Android app just to be sure.
  4. Also in recent years, file access between apps has been locked down a lot via SELinux to prevent bad actors. Pretty sure /sdcard itself is inaccessible and Android 10 and 11 go further and apps get their own isolated view of the previously shared directories under /sdcard (e.g. Pictures).
cpovirk commented 3 years ago

Thanks, Pete!

My mistake about the getProperties() docs. Hopefully a "normal" reader is more likely to see the header than I was: I was grepping the Android sources for java.io.tmpdir and /sdcard, so I saw those lines in isolation (and in source code), rather than in the rendered table.

It's also nice to great to hear that SELinux has been restricting access for a while. I was probably too snarky above -- sorry. I had seen a pretty old comment that suggested things were worse. They may well have been worse then, but it's been many years.

Thanks also for running test with a toy app. I'd been meaning to do that, myself, but didn't expect to get around to it imminently.

cpovirk commented 3 years ago

One other thought on that Android front:

It's great that the app's cache directory is already the value of java.io.tmpdir nowdays. First, again, given that java.io.tmpdir is now locked down, Android apps can safely use that directory, so there's no particular danger from createTempDir under Android. But also, if those users are migrating off createTempDir, they can continue to create that directory in java.io.tmpdir without any danger from putting anything new in that directory. (Maybe that's false for very old versions of Android. But realistically, (a) old versions may not see any fixes that anyone makes to an app nowadays, and (b) old versions may have other security problems beyond just this.) And it's also nice that Android apps can simply look up java.io.tmpdir without needing access to a Context instance. That should ease migration.

Of my Android concerns above, I'm now down to:

If we add a createArbitrarilyNamedDirectory method, we can probably (a) migrate all Google code and (b) give actionable instructions to non-Google users.

JLLeitschuh commented 3 years ago

I've asked some android developers to follow up here on the android question. Hopefully I can find someone with actual real devices they can test this on for us across the past 5-6 android versions.


the need for a Files.createArbitrarilyNamedDirectory(File parent) that keeps trying different directory names

Why is this even needed? Doesn't Java 7's Files api already offer this functionality?

prbprbprb commented 3 years ago

I had seen a pretty old comment that suggested things were worse. They may well have been worse then.

Very possible :) I've only worked on Android for a couple of years so have no direct knowledge, but nowadays the platform security team are certainly very passionate about protecting apps from each other, and even from vendors (including Google).

Thanks also for running test with a toy app. I'd been meaning to do that, myself

I have a sketchpad app I wrote for exactly this kind of thing which I keep meaning to get open sourced, so 'twas as simple as

@Sketch
public class TmpFiles extends BaseSketch {
  @Override
  public void go() throws Exception {
    out(System.getProperty("java.io.tmpdir"));
    out(File.createTempFile("foo", ".txt").getAbsolutePath());
  }
}

Hopefully I can find someone with actual real devices they can test this on for us

I didn't test on any real devices, but I would be very surprised if any vendors had patches in this area. Most of the real devices I have to hand are Pixel and I know they don't differ from AOSP. I did do the same code archeology as Chris and came to the same conclusions, i.e. it was changed between ICS and JB and has been this way ever since. Pro tip: Android Code Search (see my link from last night) lets you browse earlier releases too :)

Why is this even needed?

I'm a bit confused there too.

cpovirk commented 3 years ago

Right, Java 7's java.nio.file offers this functionality.

However, that wasn't added to Android until API Level 26 (Oreo, 2017). (Contrast to some other Java 7 APIs, which were added back in API Level 19.) In the year-old numbers I linked above, we still had 40% of Android devices on a version older than that. And certainly some apps inside Google still target older versions. (I may still see one that targets API Level 14! But it's possible that that's some kind of demo/test app, as I've heard that some other API Level 14 apps are.) In any case, whatever the exact number is, it's high enough that developers still target apps at older versions. So we can't assume that Google developers or other developers are able to use java.nio.file, and so they need something like a createArbitrarilyNamedDirectory to migrate to.

Certainly people who can use java.nio.file would be better served by doing that. And some people could put their files directly into java.io.tmpdir/getCacheDir. But given that we've already caused people issues, we'd certainly rather make the migration process as smooth as possible, without any unnecessary "If you target API Level 26+, then do this; otherwise, do that" steps.

JLLeitschuh commented 3 years ago

So two comments about this thread, because I think it's getting out of hand and off focus.

  1. I think this thread needs a "Android Security Implications" post that specifically summarizes the entirety of the discussion above. I don't want a poor security team lead who handle this vulnerability read the novel we've constructed above to fully grock whether or not android is vulnerable. I suggest that someone from Google write something up that fully describes the currently understood security risks of this vulnerability with respect to Android. What versions you may be vulnerable under, and mitigations for Android users. With that write up, do one of the following:
    1. Edit my top post to add that contents up front.
    2. Add that comment to this thread, and I'll link to it from the top issue with the header "Implications for Android"
  2. I suggest that the discussion about adding a replacement method be moved to a separate GH issue that is wholly independent from that one. If you need my insights on that thread, I'm more than happy to follow it and provide my knowledge where I can.

Per point one, I think that once the vulnerable method is fully removed, (and only at that point) it would be appropriate to create an official GitHub Security advisory under this repository fully detailing the vulnerability and utilize the existing CVE. That way, GitHub's dependabot has an actual version that they can force users to update to that is technically "fixed".

Thoughts? This is just a proposal. But something that will hopefully simplify the lives of downstream viewers of this vulnerability just trying to figure out how to fix their code.

prbprbprb commented 3 years ago

Re (1) my understanding of the Android situation prior to any Guava fix is this (please correct if wrong):

If needed I can engage the Android ISE team to see what action they feel is necessary. Note that the vulnerable versions are long out of support so any action is likely to be limited to an advisory. Or if you want to be in the loop, you can report yourself at https://source.android.com/security/overview/updates-resources#report-issues

cpovirk commented 3 years ago

The first release that is not impacted was JB, rather than ICS, right?

prbprbprb commented 3 years ago

Oops, that's correct, thanks! Edit: updated the original post for clarity

JLLeitschuh commented 3 years ago

Also, if the java.io.tmpdir was at any point /sdcard, according to a security researcher I spoke to:

the file system is etx3/4/FAT but since root on an external device means you can modify root owned files on sdcard, the [sic. posix] checks are useless

From this I interpret that for any situation where java.io.tmpdir was set to /sdcard this method is vulnerable.

Also from that researcher:

SDcard is used for storing bigger files since, it is not by default available on all devices, it is not used in code unless absolutely necessary

I think as a part of this, it would be nice to have a nice clear table that details what the value of java.io.tmpdir was by default on android. That way this is completely clear. Something like:

Android Version `java.io.tmpdir` value
Jelly Bean ...
ICS ...
nick-someone commented 3 years ago

@prbprbprb

Guava createTempDir() creates world readable directories (mode 755)

It would be more correct to say (although I don't know if there's an effective difference in this situation) that createTempDir() uses the permissions inherited from the surrounding directory. It doesn't do any explicit permissions modification.