diff --git a/crypto/bio/bio_test.cc b/crypto/bio/bio_test.cc index c9e0ae025..7115b9819 100644 --- a/crypto/bio/bio_test.cc +++ b/crypto/bio/bio_test.cc @@ -24,6 +24,7 @@ #include #include "../internal.h" +#include "../test/file_util.h" #include "../test/test_util.h" #if !defined(OPENSSL_WINDOWS) @@ -37,6 +38,7 @@ #include #else #include +#include OPENSSL_MSVC_PRAGMA(warning(push, 3)) #include #include @@ -633,50 +635,59 @@ TEST(BIOTest, Gets) { check_bio_gets(bio.get()); } - struct FileCloser { - void operator()(FILE *f) const { fclose(f); } - }; - using ScopedFILE = std::unique_ptr; - ScopedFILE file(tmpfile()); -#if defined(OPENSSL_ANDROID) - // On Android, when running from an APK, |tmpfile| does not work. See - // b/36991167#comment8. - if (!file) { - fprintf(stderr, "tmpfile failed: %s (%d). Skipping file-based tests.\n", - strerror(errno), errno); - continue; - } -#else - ASSERT_TRUE(file); -#endif + if (!SkipTempFileTests()) { + TemporaryFile file; + ASSERT_TRUE(file.Init(t.bio)); - if (!t.bio.empty()) { - ASSERT_EQ(1u, - fwrite(t.bio.data(), t.bio.size(), /*nitems=*/1, file.get())); - ASSERT_EQ(0, fseek(file.get(), 0, SEEK_SET)); - } + // TODO(crbug.com/boringssl/585): If the line has an embedded NUL, file + // BIOs do not currently report the answer correctly. + if (t.bio.find('\0') == std::string::npos) { + SCOPED_TRACE("file"); - // TODO(crbug.com/boringssl/585): If the line has an embedded NUL, file - // BIOs do not currently report the answer correctly. - if (t.bio.find('\0') == std::string::npos) { - SCOPED_TRACE("file"); - bssl::UniquePtr bio(BIO_new_fp(file.get(), BIO_NOCLOSE)); - ASSERT_TRUE(bio); - check_bio_gets(bio.get()); - } + // Test |BIO_new_file|. + bssl::UniquePtr bio(BIO_new_file(file.path().c_str(), "r")); + ASSERT_TRUE(bio); + check_bio_gets(bio.get()); - ASSERT_EQ(0, fseek(file.get(), 0, SEEK_SET)); + // Test |BIO_NOCLOSE|. + ScopedFILE file_obj = file.Open("r"); + ASSERT_TRUE(file_obj); + bio.reset(BIO_new_fp(file_obj.get(), BIO_NOCLOSE)); + ASSERT_TRUE(bio); + check_bio_gets(bio.get()); - { - SCOPED_TRACE("fd"); + // Test |BIO_CLOSE|. + file_obj = file.Open("r"); + ASSERT_TRUE(file_obj); + bio.reset(BIO_new_fp(file_obj.get(), BIO_CLOSE)); + ASSERT_TRUE(bio); + file_obj.release(); // |BIO_new_fp| took ownership on success. + check_bio_gets(bio.get()); + } + + { + SCOPED_TRACE("fd"); #if defined(OPENSSL_WINDOWS) - int fd = _fileno(file.get()); + int open_flags = _O_RDONLY | _O_BINARY; #else - int fd = fileno(file.get()); + int open_flags = O_RDONLY; #endif - bssl::UniquePtr bio(BIO_new_fd(fd, BIO_NOCLOSE)); - ASSERT_TRUE(bio); - check_bio_gets(bio.get()); + + // Test |BIO_NOCLOSE|. + ScopedFD fd = file.OpenFD(open_flags); + ASSERT_TRUE(fd.is_valid()); + bssl::UniquePtr bio(BIO_new_fd(fd.get(), BIO_NOCLOSE)); + ASSERT_TRUE(bio); + check_bio_gets(bio.get()); + + // Test |BIO_CLOSE|. + fd = file.OpenFD(open_flags); + ASSERT_TRUE(fd.is_valid()); + bio.reset(BIO_new_fd(fd.get(), BIO_CLOSE)); + ASSERT_TRUE(bio); + fd.release(); // |BIO_new_fd| took ownership on success. + check_bio_gets(bio.get()); + } } } diff --git a/crypto/test/file_util.cc b/crypto/test/file_util.cc new file mode 100644 index 000000000..ede0d763f --- /dev/null +++ b/crypto/test/file_util.cc @@ -0,0 +1,163 @@ +/* Copyright (c) 2023, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#include "file_util.h" + +#include + +#if defined(OPENSSL_WINDOWS) +OPENSSL_MSVC_PRAGMA(warning(push, 3)) +#include +OPENSSL_MSVC_PRAGMA(warning(pop)) +#else +#include +#include +#include +#endif + +#include + +#include "test_util.h" + + +#if defined(OPENSSL_WINDOWS) +static void PrintLastError(const char *s) { + DWORD error = GetLastError(); + char *buffer; + DWORD len = FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, error, 0, + reinterpret_cast(&buffer), 0, nullptr); + std::string msg = "unknown error"; + if (len > 0) { + msg.assign(buffer, len); + while (!msg.empty() && (msg.back() == '\r' || msg.back() == '\n')) { + msg.resize(msg.size() - 1); + } + } + LocalFree(buffer); + fprintf(stderr, "%s: %s (0x%lx)\n", s, msg.c_str(), error); +} +#endif // OPENSSL_WINDOWS + +// GetTempDir returns the path to the temporary directory, or the empty string +// on error. On success, the result will include the directory separator. +static std::string GetTempDir() { +#if defined(OPENSSL_WINDOWS) + char buf[MAX_PATH + 1]; + DWORD len = GetTempPathA(sizeof(buf), buf); + return std::string(buf, len); +#else + const char *tmpdir = getenv("TMPDIR"); + if (tmpdir != nullptr && *tmpdir != '\0') { + std::string ret = tmpdir; + if (ret.back() != '/') { + ret.push_back('/'); + } + return ret; + } +#if defined(OPENSSL_ANDROID) + return "/data/local/tmp/"; +#else + return "/tmp/"; +#endif +#endif +} + +bool SkipTempFileTests() { +#if defined(OPENSSL_ANDROID) + // When running in an APK context, /data/local/tmp is unreadable. Android + // versions before https://android-review.googlesource.com/c/1821337 do not + // set TMPDIR to a suitable replacement. + if (getenv("TMPDIR") == nullptr) { + static bool should_skip = [] { + TemporaryFile file; + return !file.Init(); + }(); + if (should_skip) { + fprintf(stderr, "Skipping tests with temporary files.\n"); + return true; + } + } +#endif + return false; +} + +TemporaryFile::~TemporaryFile() { +#if defined(OPENSSL_WINDOWS) + if (!path_.empty() && !DeleteFileA(path_.c_str())) { + PrintLastError("Could not delete file"); + } +#else + if (!path_.empty() && unlink(path_.c_str()) != 0) { + perror("Could not delete file"); + } +#endif +} + +bool TemporaryFile::Init(bssl::Span content) { + std::string temp_dir = GetTempDir(); + if (temp_dir.empty()) { + return false; + } + +#if defined(OPENSSL_WINDOWS) + char path[MAX_PATH]; + if (GetTempFileNameA(temp_dir.c_str(), "bssl", + /*uUnique=*/0, path) == 0) { + PrintLastError("Could not create temporary"); + return false; + } + path_ = path; +#else + std::string path = temp_dir + "bssl_tmp_file.XXXXXX"; + // TODO(davidben): Use |path.data()| when we require C++17. + int fd = mkstemp(&path[0]); + if (fd < 0) { + perror("Could not create temporary file"); + return false; + } + close(fd); + path_ = std::move(path); +#endif + + ScopedFILE file = Open("wb"); + if (file == nullptr) { + perror("Could not open temporary file"); + return false; + } + if (!content.empty() && + fwrite(content.data(), content.size(), /*nitems=*/1, file.get()) != 1) { + perror("Could not write temporary file"); + return false; + } + return true; +} + +ScopedFILE TemporaryFile::Open(const char *mode) const { + if (path_.empty()) { + return nullptr; + } + return ScopedFILE(fopen(path_.c_str(), mode)); +} + +ScopedFD TemporaryFile::OpenFD(int flags) const { + if (path_.empty()) { + return ScopedFD(); + } +#if defined(OPENSSL_WINDOWS) + return ScopedFD(_open(path_.c_str(), flags)); +#else + return ScopedFD(open(path_.c_str(), flags)); +#endif +} diff --git a/crypto/test/file_util.h b/crypto/test/file_util.h new file mode 100644 index 000000000..51752b4fa --- /dev/null +++ b/crypto/test/file_util.h @@ -0,0 +1,113 @@ +/* Copyright (c) 2023, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#ifndef OPENSSL_HEADER_CRYPTO_TEST_FILE_UTIL_H +#define OPENSSL_HEADER_CRYPTO_TEST_FILE_UTIL_H + +#include + +#include +#include +#include + +#include + +#if defined(OPENSSL_WINDOWS) +#include +#else +#include +#endif + + +struct FileDeleter { + void operator()(FILE *f) const { + if (f != nullptr) { + fclose(f); + } + } +}; + +using ScopedFILE = std::unique_ptr; + +class ScopedFD { + public: + ScopedFD() = default; + explicit ScopedFD(int fd) : fd_(fd) {} + ~ScopedFD() { reset(); } + + ScopedFD(ScopedFD &&other) { *this = std::move(other); } + ScopedFD &operator=(ScopedFD other) { + reset(other.release()); + return *this; + } + + bool is_valid() const { return fd_ >= 0; } + int get() const { return fd_; } + + int release() { return std::exchange(fd_, -1); } + void reset(int fd = -1) { + if (is_valid()) { +#if defined(OPENSSL_WINDOWS) + _close(fd_); +#else + close(fd_); +#endif + } + fd_ = fd; + } + + private: + int fd_ = -1; +}; + +// SkipTempFileTests returns true and prints a warning if tests involving +// temporary files should be skipped because of platform issues. +bool SkipTempFileTests(); + +// TemporaryFile manages a temporary file for testing. +class TemporaryFile { + public: + TemporaryFile() = default; + ~TemporaryFile(); + + TemporaryFile(TemporaryFile &other) { *this = std::move(other); } + TemporaryFile& operator=(TemporaryFile&&other) { + // Ensure |path_| is empty so it doesn't try to delete the File. + path_ = std::exchange(other.path_, {}); + return *this; + } + + // Init initializes the temporary file with the specified content. It returns + // true on success and false on error. On error, callers should call + // |IgnoreTempFileErrors| to determine whether to ignore the error. + bool Init(bssl::Span content = {}); + bool Init(const std::string &content) { + return Init(bssl::MakeConstSpan( + reinterpret_cast(content.data()), content.size())); + } + + // Open opens the file as a |FILE| with the specified mode. + ScopedFILE Open(const char *mode) const; + + // Open opens the file as a file descriptor with the specified flags. + ScopedFD OpenFD(int flags) const; + + // path returns the path to the temporary file. + const std::string &path() const { return path_; } + + private: + std::string path_; +}; + +#endif // OPENSSL_HEADER_CRYPTO_TEST_FILE_UTIL_H diff --git a/sources.cmake b/sources.cmake index d32f24bdc..262b3fdd8 100644 --- a/sources.cmake +++ b/sources.cmake @@ -433,6 +433,7 @@ set( crypto/test/abi_test.cc crypto/test/file_test.cc + crypto/test/file_util.cc crypto/test/test_util.cc crypto/test/wycheproof_util.cc ) diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index 3595204b7..0b4ad3ccd 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc @@ -43,6 +43,7 @@ #include "internal.h" #include "../crypto/internal.h" +#include "../crypto/test/file_util.h" #include "../crypto/test/test_util.h" #if defined(OPENSSL_WINDOWS) @@ -8728,14 +8729,17 @@ xNCwyMX9mtdXdQicOfNjIGUCD5OLV5PgHFPRKiHHioBAhg== } } -#if defined(OPENSSL_LINUX) || defined(OPENSSL_APPLE) TEST(SSLTest, EmptyClientCAList) { - // Use /dev/null on POSIX systems as an empty file. + if (SkipTempFileTests()) { + GTEST_SKIP(); + } + + TemporaryFile empty; + ASSERT_TRUE(empty.Init()); bssl::UniquePtr names( - SSL_load_client_CA_file("/dev/null")); + SSL_load_client_CA_file(empty.path().c_str())); EXPECT_FALSE(names); } -#endif // OPENSSL_LINUX || OPENSSL_APPLE TEST(SSLTest, EmptyWriteBlockedOnHandshakeData) { bssl::UniquePtr client_ctx(SSL_CTX_new(TLS_method()));