Add some utilities for testing temporary files

Avoid rewriting the FILE scoper, and deal with the Android problem in
one place. This header will also, in the next CL, be the home for a
temporary directory helper for hash_dir.

Change-Id: I4be69ef6c2ac3443b80ee8852bcce4078bf7f118
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/66007
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
This commit is contained in:
David Benjamin
2024-02-22 22:30:35 +00:00
committed by Boringssl LUCI CQ
parent c06c4d5ea6
commit 0ff377a30c
5 changed files with 333 additions and 41 deletions
+48 -37
View File
@@ -24,6 +24,7 @@
#include <openssl/mem.h>
#include "../internal.h"
#include "../test/file_util.h"
#include "../test/test_util.h"
#if !defined(OPENSSL_WINDOWS)
@@ -37,6 +38,7 @@
#include <unistd.h>
#else
#include <io.h>
#include <fcntl.h>
OPENSSL_MSVC_PRAGMA(warning(push, 3))
#include <winsock2.h>
#include <ws2tcpip.h>
@@ -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<FILE, FileCloser>;
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(BIO_new_fp(file.get(), BIO_NOCLOSE));
ASSERT_TRUE(bio);
check_bio_gets(bio.get());
}
// Test |BIO_new_file|.
bssl::UniquePtr<BIO> 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(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(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());
}
}
}
+163
View File
@@ -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 <stdlib.h>
#if defined(OPENSSL_WINDOWS)
OPENSSL_MSVC_PRAGMA(warning(push, 3))
#include <windows.h>
OPENSSL_MSVC_PRAGMA(warning(pop))
#else
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
#include <openssl/rand.h>
#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<char *>(&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<const uint8_t> 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
}
+113
View File
@@ -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 <stdio.h>
#include <memory>
#include <string>
#include <utility>
#include <openssl/span.h>
#if defined(OPENSSL_WINDOWS)
#include <io.h>
#else
#include <unistd.h>
#endif
struct FileDeleter {
void operator()(FILE *f) const {
if (f != nullptr) {
fclose(f);
}
}
};
using ScopedFILE = std::unique_ptr<FILE, FileDeleter>;
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<const uint8_t> content = {});
bool Init(const std::string &content) {
return Init(bssl::MakeConstSpan(
reinterpret_cast<const uint8_t *>(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
+1
View File
@@ -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
)
+8 -4
View File
@@ -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<STACK_OF(X509_NAME)> 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<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));