Projects
Mega:24.03
guava
_service:tar_scm:CVE-2023-2976.patch
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:tar_scm:CVE-2023-2976.patch of Package guava
From feb83a1c8fd2e7670b244d5afd23cba5aca43284 Mon Sep 17 00:00:00 2001 From: cpovirk <cpovirk@google.com> Date: Thu, 25 May 2023 13:18:00 -0700 Subject: [PATCH] Restrict permissions when creating temporary files and directories, or fail if that's not possible. (Also, check that the provided `fileThreshold` is non-negative.) - Fixes https://github.com/google/guava/issues/2575 - Fixes https://github.com/google/guava/issues/4011 RELNOTES=Reimplemented `Files.createTempDir` and `FileBackedOutputStream` to further address [CVE-2020-8908](https://github.com/google/guava/issues/4011) and [Guava issue #2575](https://github.com/google/guava/issues/2575) (CVE forthcoming). PiperOrigin-RevId: 535359233 Refer: https://github.com/google/guava/commit/feb83a1c8fd2e7670b244d5afd23cba5aca43284 --- .../common/io/FileBackedOutputStreamTest.java | 27 +++ .../common/io/FilesCreateTempDirTest.java | 41 +++- .../common/io/FileBackedOutputStream.java | 22 ++- guava/src/com/google/common/io/Files.java | 51 ++--- .../common/io/IgnoreJRERequirement.java | 30 +++ .../com/google/common/io/TempFileCreator.java | 176 ++++++++++++++++++ 6 files changed, 302 insertions(+), 45 deletions(-) create mode 100644 guava/src/com/google/common/io/IgnoreJRERequirement.java create mode 100644 guava/src/com/google/common/io/TempFileCreator.java diff --git a/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java b/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java index 6841a41..2dae0ed 100644 --- a/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java +++ b/guava-tests/test/com/google/common/io/FileBackedOutputStreamTest.java @@ -17,10 +17,18 @@ package com.google.common.io; +import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; +import static org.junit.Assert.assertThrows; + import com.google.common.testing.GcFinalization; import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFileAttributes; import java.util.Arrays; /** @@ -61,10 +69,21 @@ public class FileBackedOutputStreamTest extends IoTestCase { // Write data to go over the threshold if (chunk2 > 0) { + if (JAVA_IO_TMPDIR.value().equals("/sdcard")) { + assertThrows(IOException.class, () -> write(out, data, chunk1, chunk2, singleByte)); + return; + } write(out, data, chunk1, chunk2, singleByte); file = out.getFile(); assertEquals(dataSize, file.length()); assertTrue(file.exists()); + assertThat(file.getName()).contains("FileBackedOutputStream"); + if (!isAndroid()) { + PosixFileAttributes attributes = + java.nio.file.Files.getFileAttributeView(file.toPath(), PosixFileAttributeView.class) + .readAttributes(); + assertThat(attributes.permissions()).containsExactly(OWNER_READ, OWNER_WRITE); + } } out.close(); @@ -133,6 +152,10 @@ public class FileBackedOutputStreamTest extends IoTestCase { FileBackedOutputStream out = new FileBackedOutputStream(50); ByteSource source = out.asByteSource(); + if (JAVA_IO_TMPDIR.value().equals("/sdcard")) { + assertThrows(IOException.class, () -> out.write(data)); + return; + } out.write(data); assertTrue(Arrays.equals(data, source.read())); @@ -164,4 +187,8 @@ public class FileBackedOutputStreamTest extends IoTestCase { out.close(); } + + private static boolean isAndroid() { + return System.getProperty("java.runtime.name", "").contains("Android"); + } } diff --git a/guava-tests/test/com/google/common/io/FilesCreateTempDirTest.java b/guava-tests/test/com/google/common/io/FilesCreateTempDirTest.java index 557689e..62098ef 100644 --- a/guava-tests/test/com/google/common/io/FilesCreateTempDirTest.java +++ b/guava-tests/test/com/google/common/io/FilesCreateTempDirTest.java @@ -16,9 +16,18 @@ package com.google.common.io; +import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR; import static com.google.common.truth.Truth.assertThat; +import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; +import static org.junit.Assert.assertThrows; import java.io.File; +import java.io.IOException; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFileAttributes; +import junit.framework.TestCase; /** * Unit test for {@link Files#createTempDir}. @@ -26,12 +35,32 @@ import java.io.File; * @author Chris Nokleberg */ -public class FilesCreateTempDirTest extends IoTestCase { - public void testCreateTempDir() { +@SuppressWarnings("deprecation") // tests of a deprecated method +public class FilesCreateTempDirTest extends TestCase { + public void testCreateTempDir() throws IOException { + if (JAVA_IO_TMPDIR.value().equals("/sdcard")) { + assertThrows(IllegalStateException.class, Files::createTempDir); + return; + } File temp = Files.createTempDir(); - assertTrue(temp.exists()); - assertTrue(temp.isDirectory()); - assertThat(temp.listFiles()).isEmpty(); - assertTrue(temp.delete()); + try { + assertTrue(temp.exists()); + assertTrue(temp.isDirectory()); + assertThat(temp.listFiles()).isEmpty(); + + if (isAndroid()) { + return; + } + PosixFileAttributes attributes = + java.nio.file.Files.getFileAttributeView(temp.toPath(), PosixFileAttributeView.class) + .readAttributes(); + assertThat(attributes.permissions()).containsExactly(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE); + } finally { + assertTrue(temp.delete()); + } + } + + private static boolean isAndroid() { + return System.getProperty("java.runtime.name", "").contains("Android"); } } diff --git a/guava/src/com/google/common/io/FileBackedOutputStream.java b/guava/src/com/google/common/io/FileBackedOutputStream.java index 9912e2f..ef553a0 100644 --- a/guava/src/com/google/common/io/FileBackedOutputStream.java +++ b/guava/src/com/google/common/io/FileBackedOutputStream.java @@ -14,6 +14,7 @@ package com.google.common.io; +import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; import com.google.common.annotations.Beta; @@ -34,6 +35,14 @@ import javax.annotation.CheckForNull; * An {@link OutputStream} that starts buffering to a byte array, but switches to file buffering * once the data reaches a configurable size. * + * <p>When this stream creates a temporary file, it restricts the file's permissions to the current + * user or, in the case of Android, the current app. If that is not possible (as is the case under + * the very old Android Ice Cream Sandwich release), then this stream throws an exception instead of + * creating a file that would be more accessible. (This behavior is new in Guava 32.0.0. Previous + * versions would create a file that is more accessible, as discussed in <a + * href="https://github.com/google/guava/issues/2575">Guava issue 2575</a>. TODO: b/283778848 - Fill + * in CVE number once it's available.) + * * <p>Temporary files created by this stream may live in the local filesystem until either: * * <ul> @@ -57,7 +66,6 @@ public final class FileBackedOutputStream extends OutputStream { private final int fileThreshold; private final boolean resetOnFinalize; private final ByteSource source; - @CheckForNull private final File parentDirectory; @GuardedBy("this") private OutputStream out; @@ -93,6 +101,7 @@ public final class FileBackedOutputStream extends OutputStream { * {@link ByteSource} returned by {@link #asByteSource} is finalized. * * @param fileThreshold the number of bytes before the stream should switch to buffering to a file + * @throws IllegalArgumentException if {@code fileThreshold} is negative */ public FileBackedOutputStream(int fileThreshold) { this(fileThreshold, false); @@ -105,16 +114,13 @@ public final class FileBackedOutputStream extends OutputStream { * @param fileThreshold the number of bytes before the stream should switch to buffering to a file * @param resetOnFinalize if true, the {@link #reset} method will be called when the {@link * ByteSource} returned by {@link #asByteSource} is finalized. + * @throws IllegalArgumentException if {@code fileThreshold} is negative */ public FileBackedOutputStream(int fileThreshold, boolean resetOnFinalize) { - this(fileThreshold, resetOnFinalize, null); - } - - private FileBackedOutputStream( - int fileThreshold, boolean resetOnFinalize, @CheckForNull File parentDirectory) { + checkArgument( + fileThreshold >= 0, "fileThreshold must be non-negative, but was %s", fileThreshold); this.fileThreshold = fileThreshold; this.resetOnFinalize = resetOnFinalize; - this.parentDirectory = parentDirectory; memory = new MemoryOutput(); out = memory; @@ -225,7 +231,7 @@ public final class FileBackedOutputStream extends OutputStream { @GuardedBy("this") private void update(int len) throws IOException { if (memory != null && (memory.getCount() + len > fileThreshold)) { - File temp = File.createTempFile("FileBackedOutputStream", null, parentDirectory); + File temp = TempFileCreator.INSTANCE.createTempFile("FileBackedOutputStream"); if (resetOnFinalize) { // Finalizers are not guaranteed to be called on system shutdown; // this is insurance. diff --git a/guava/src/com/google/common/io/Files.java b/guava/src/com/google/common/io/Files.java index ba5528f..2bc67ab 100644 --- a/guava/src/com/google/common/io/Files.java +++ b/guava/src/com/google/common/io/Files.java @@ -70,9 +70,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; @ElementTypesAreNonnullByDefault public final class Files { - /** Maximum loop count when creating temp directories. */ - private static final int TEMP_DIR_ATTEMPTS = 10000; - private Files() {} /** @@ -394,17 +391,19 @@ public final class Files { * Atomically creates a new directory somewhere beneath the system's temporary directory (as * defined by the {@code java.io.tmpdir} system property), and returns its name. * + * <p>The temporary directory is created with permissions restricted to the current user or, in + * the case of Android, the current app. If that is not possible (as is the case under the very + * old Android Ice Cream Sandwich release), then this method throws an exception instead of + * creating a directory that would be more accessible. (This behavior is new in Guava 32.0.0. + * Previous versions would create a directory that is more accessible, as discussed in <a + * href="https://github.com/google/guava/issues/4011">CVE-2020-8908</a>.) + * * <p>Use this method instead of {@link File#createTempFile(String, String)} when you wish to * create a directory, not a regular file. A common pitfall is to call {@code createTempFile}, * delete the file and create a directory in its place, but this leads a race condition which can * be exploited to create security vulnerabilities, especially when executable files are to be * written into the directory. * - * <p>Depending on the environmment that this code is run in, the system temporary directory (and - * thus the directory this method creates) may be more visible that a program would like - files - * written to this directory may be read or overwritten by hostile programs running on the same - * machine. - * * <p>This method assumes that the temporary volume is writable, has free inodes and free blocks, * and that it will not be called thousands of times per second. * @@ -413,35 +412,25 @@ public final class Files { * * @return the newly-created directory * @throws IllegalStateException if the directory could not be created + * @throws UnsupportedOperationException if the system does not support creating temporary + * directories securely * @deprecated For Android users, see the <a * href="https://developer.android.com/training/data-storage" target="_blank">Data and File * Storage overview</a> to select an appropriate temporary directory (perhaps {@code - * context.getCacheDir()}). For developers on Java 7 or later, use {@link - * java.nio.file.Files#createTempDirectory}, transforming it to a {@link File} using {@link - * java.nio.file.Path#toFile() toFile()} if needed. + * context.getCacheDir()}), and create your own directory under that. (For example, you might + * use {@code new File(context.getCacheDir(), "directoryname").mkdir()}, or, if you need an + * arbitrary number of temporary directories, you might have to generate multiple directory + * names in a loop until {@code mkdir()} returns {@code true}.) For developers on Java 7 or + * later, use {@link java.nio.file.Files#createTempDirectory}, transforming it to a {@link + * File} using {@link java.nio.file.Path#toFile() toFile()} if needed. To restrict permissions + * as this method does, pass {@code + * PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"))} to your + * call to {@code createTempDirectory}. */ @Beta @Deprecated public static File createTempDir() { - File baseDir = new File(System.getProperty("java.io.tmpdir")); - @SuppressWarnings("GoodTime") // reading system time without TimeSource - String baseName = System.currentTimeMillis() + "-"; - - for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { - File tempDir = new File(baseDir, baseName + counter); - if (tempDir.mkdir()) { - return tempDir; - } - } - throw new IllegalStateException( - "Failed to create directory within " - + TEMP_DIR_ATTEMPTS - + " attempts (tried " - + baseName - + "0 to " - + baseName - + (TEMP_DIR_ATTEMPTS - 1) - + ')'); + return TempFileCreator.INSTANCE.createTempDir(); } /** diff --git a/guava/src/com/google/common/io/IgnoreJRERequirement.java b/guava/src/com/google/common/io/IgnoreJRERequirement.java new file mode 100644 index 0000000..b1b8e10 --- /dev/null +++ b/guava/src/com/google/common/io/IgnoreJRERequirement.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The Guava Authors + * + * 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.google.common.io; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Target; + +/** + * Disables Animal Sniffer's checking of compatibility with older versions of Java/Android. + * + * <p>Each package's copy of this annotation needs to be listed in our {@code pom.xml}. + */ +@Target({METHOD, CONSTRUCTOR, TYPE}) +@ElementTypesAreNonnullByDefault +@interface IgnoreJRERequirement {} diff --git a/guava/src/com/google/common/io/TempFileCreator.java b/guava/src/com/google/common/io/TempFileCreator.java new file mode 100644 index 0000000..a28a0af --- /dev/null +++ b/guava/src/com/google/common/io/TempFileCreator.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * 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.google.common.io; + +import static com.google.common.base.StandardSystemProperty.JAVA_IO_TMPDIR; + +import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; +import com.google.j2objc.annotations.J2ObjCIncompatible; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Set; + +/** + * Creates temporary files and directories whose permissions are restricted to the current user or, + * in the case of Android, the current app. If that is not possible (as is the case under the very + * old Android Ice Cream Sandwich release), then this class throws an exception instead of creating + * a file or directory that would be more accessible. + */ +@J2ktIncompatible +@GwtIncompatible +@J2ObjCIncompatible +@ElementTypesAreNonnullByDefault +abstract class TempFileCreator { + static final TempFileCreator INSTANCE = pickSecureCreator(); + + /** + * @throws IllegalStateException if the directory could not be created (to implement the contract + * of {@link Files#createTempDir()} + * @throws UnsupportedOperationException if the system does not support creating temporary + * directories securely + */ + abstract File createTempDir(); + + abstract File createTempFile(String prefix) throws IOException; + + private static TempFileCreator pickSecureCreator() { + try { + Class.forName("java.nio.file.Path"); + return new JavaNioCreator(); + } catch (ClassNotFoundException runningUnderAndroid) { + // Try another way. + } + + try { + int version = (int) Class.forName("android.os.Build$VERSION").getField("SDK_INT").get(null); + int jellyBean = + (int) Class.forName("android.os.Build$VERSION_CODES").getField("JELLY_BEAN").get(null); + /* + * I assume that this check can't fail because JELLY_BEAN will be present only if we're + * running under Jelly Bean or higher. But it seems safest to check. + */ + if (version < jellyBean) { + return new ThrowingCreator(); + } + + // Don't merge these catch() blocks, let alone use ReflectiveOperationException directly: + // b/65343391 + } catch (NoSuchFieldException e) { + // The JELLY_BEAN field doesn't exist because we're running on a version before Jelly Bean :) + return new ThrowingCreator(); + } catch (ClassNotFoundException e) { + // Should be impossible, but we want to return *something* so that class init succeeds. + return new ThrowingCreator(); + } catch (IllegalAccessException e) { + // ditto + return new ThrowingCreator(); + } + + // Android isolates apps' temporary directories since Jelly Bean: + // https://github.com/google/guava/issues/4011#issuecomment-770020802 + // So we can create files there with any permissions and still get security from the isolation. + return new JavaIoCreator(); + } + + @IgnoreJRERequirement // used only when Path is available + private static final class JavaNioCreator extends TempFileCreator { + private static final FileAttribute<Set<PosixFilePermission>> RWX_USER_ONLY = + PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------")); + private static final FileAttribute<Set<PosixFilePermission>> RW_USER_ONLY = + PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-------")); + + @Override + File createTempDir() { + try { + return java.nio.file.Files.createTempDirectory( + Paths.get(JAVA_IO_TMPDIR.value()), /* prefix= */ null, RWX_USER_ONLY) + .toFile(); + } catch (IOException e) { + throw new IllegalStateException("Failed to create directory", e); + } + } + + @Override + File createTempFile(String prefix) throws IOException { + return java.nio.file.Files.createTempFile( + Paths.get(JAVA_IO_TMPDIR.value()), + /* prefix= */ prefix, + /* suffix= */ null, + RW_USER_ONLY) + .toFile(); + } + } + + private static final class JavaIoCreator extends TempFileCreator { + @Override + File createTempDir() { + File baseDir = new File(JAVA_IO_TMPDIR.value()); + @SuppressWarnings("GoodTime") // reading system time without TimeSource + String baseName = System.currentTimeMillis() + "-"; + + for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { + File tempDir = new File(baseDir, baseName + counter); + if (tempDir.mkdir()) { + return tempDir; + } + } + throw new IllegalStateException( + "Failed to create directory within " + + TEMP_DIR_ATTEMPTS + + " attempts (tried " + + baseName + + "0 to " + + baseName + + (TEMP_DIR_ATTEMPTS - 1) + + ')'); + } + + @Override + File createTempFile(String prefix) throws IOException { + return File.createTempFile( + /* prefix= */ prefix, + /* suffix= */ null, + /* directory= */ null /* defaults to java.io.tmpdir */); + } + + /** Maximum loop count when creating temp directories. */ + private static final int TEMP_DIR_ATTEMPTS = 10000; + } + + private static final class ThrowingCreator extends TempFileCreator { + private static final String MESSAGE = + "Guava cannot securely create temporary files or directories under SDK versions before" + + " Jelly Bean. You can create one yourself, either in the insecure default directory" + + " or in a more secure directory, such as context.getCacheDir(). For more information," + + " see the Javadoc for Files.createTempDir()."; + + @Override + File createTempDir() { + throw new IllegalStateException(MESSAGE); + } + + @Override + File createTempFile(String prefix) throws IOException { + throw new IOException(MESSAGE); + } + } + + private TempFileCreator() {} +} -- 2.41.0
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2