1 // Copyright 2023 The BoringSSL Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "file_util.h"
16 
17 #include <stdlib.h>
18 
19 #if defined(OPENSSL_WINDOWS)
20 #include <windows.h>
21 #else
22 #include <fcntl.h>
23 #include <sys/stat.h>
24 #include <unistd.h>
25 #endif
26 
27 #include <openssl/rand.h>
28 
29 #include "test_util.h"
30 
31 
32 BSSL_NAMESPACE_BEGIN
33 
34 #if defined(OPENSSL_WINDOWS)
PrintLastError(const char * s)35 static void PrintLastError(const char *s) {
36   DWORD error = GetLastError();
37   char *buffer;
38   DWORD len = FormatMessageA(
39       FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, error, 0,
40       reinterpret_cast<char *>(&buffer), 0, nullptr);
41   std::string msg = "unknown error";
42   if (len > 0) {
43     msg.assign(buffer, len);
44     while (!msg.empty() && (msg.back() == '\r' || msg.back() == '\n')) {
45       msg.resize(msg.size() - 1);
46     }
47   }
48   LocalFree(buffer);
49   fprintf(stderr, "%s: %s (0x%lx)\n", s, msg.c_str(), error);
50 }
51 #endif  // OPENSSL_WINDOWS
52 
53 // GetTempDir returns the path to the temporary directory, or the empty string
54 // on error. On success, the result will include the directory separator.
GetTempDir()55 static std::string GetTempDir() {
56 #if defined(OPENSSL_WINDOWS)
57   char buf[MAX_PATH + 1];
58   DWORD len = GetTempPathA(sizeof(buf), buf);
59   return std::string(buf, len);
60 #else
61   const char *tmpdir = getenv("TMPDIR");
62   if (tmpdir != nullptr && *tmpdir != '\0') {
63     std::string ret = tmpdir;
64     if (ret.back() != '/') {
65       ret.push_back('/');
66     }
67     return ret;
68   }
69 #if defined(OPENSSL_ANDROID)
70   return "/data/local/tmp/";
71 #else
72   return "/tmp/";
73 #endif
74 #endif
75 }
76 
SkipTempFileTests()77 bool SkipTempFileTests() {
78 #if defined(OPENSSL_ANDROID)
79   // When running in an APK context, /data/local/tmp is unreadable. Android
80   // versions before https://android-review.googlesource.com/c/1821337 do not
81   // set TMPDIR to a suitable replacement.
82   if (getenv("TMPDIR") == nullptr) {
83     static bool should_skip = [] {
84       TemporaryFile file;
85       return !file.Init();
86     }();
87     if (should_skip) {
88       fprintf(stderr, "Skipping tests with temporary files.\n");
89       return true;
90     }
91   }
92 #endif
93   return false;
94 }
95 
~TemporaryFile()96 TemporaryFile::~TemporaryFile() {
97 #if defined(OPENSSL_WINDOWS)
98   if (!path_.empty() && !DeleteFileA(path_.c_str())) {
99     PrintLastError("Could not delete file");
100   }
101 #else
102   if (!path_.empty() && unlink(path_.c_str()) != 0) {
103     perror("Could not delete file");
104   }
105 #endif
106 }
107 
Init(bssl::Span<const uint8_t> content)108 bool TemporaryFile::Init(bssl::Span<const uint8_t> content) {
109   std::string temp_dir = GetTempDir();
110   if (temp_dir.empty()) {
111     return false;
112   }
113 
114 #if defined(OPENSSL_WINDOWS)
115   char path[MAX_PATH];
116   if (GetTempFileNameA(temp_dir.c_str(), "bssl",
117                        /*uUnique=*/0, path) == 0) {
118     PrintLastError("Could not create temporary");
119     return false;
120   }
121   path_ = path;
122 #else
123   std::string path = temp_dir + "bssl_tmp_file.XXXXXX";
124   int fd = mkstemp(path.data());
125   if (fd < 0) {
126     perror("Could not create temporary file");
127     return false;
128   }
129   close(fd);
130   path_ = std::move(path);
131 #endif
132 
133   ScopedFILE file = Open("wb");
134   if (file == nullptr) {
135     perror("Could not open temporary file");
136     return false;
137   }
138   if (!content.empty() &&
139       fwrite(content.data(), content.size(), /*nitems=*/1, file.get()) != 1) {
140     perror("Could not write temporary file");
141     return false;
142   }
143   return true;
144 }
145 
Open(const char * mode) const146 ScopedFILE TemporaryFile::Open(const char *mode) const {
147   if (path_.empty()) {
148     return nullptr;
149   }
150   return ScopedFILE(fopen(path_.c_str(), mode));
151 }
152 
OpenFD(int flags) const153 ScopedFD TemporaryFile::OpenFD(int flags) const {
154   if (path_.empty()) {
155     return ScopedFD();
156   }
157 #if defined(OPENSSL_WINDOWS)
158   return ScopedFD(_open(path_.c_str(), flags));
159 #else
160   return ScopedFD(open(path_.c_str(), flags));
161 #endif
162 }
163 
~TemporaryDirectory()164 TemporaryDirectory::~TemporaryDirectory() {
165   if (path_.empty()) {
166     return;
167   }
168 
169 #if defined(OPENSSL_WINDOWS)
170   for (const auto &file : files_) {
171     if (!DeleteFileA(GetFilePath(file).c_str())) {
172       PrintLastError("Could not delete file");
173     }
174   }
175   if (!RemoveDirectoryA(path_.c_str())) {
176     PrintLastError("Could not delete directory");
177   }
178 #else
179   for (const auto &file : files_) {
180     if (unlink(GetFilePath(file).c_str()) != 0) {
181       perror("Could not delete file");
182     }
183   }
184   if (rmdir(path_.c_str()) != 0) {
185     perror("Could not delete directory");
186   }
187 #endif
188 }
189 
Init()190 bool TemporaryDirectory::Init() {
191   std::string path = GetTempDir();
192   if (path.empty()) {
193     return false;
194   }
195 
196 #if defined(OPENSSL_WINDOWS)
197   // For simplicity, just try the first candidate and assume the directory
198   // doesn't exist. 128 bits of cryptographically secure randomness is plenty.
199   uint8_t buf[16];
200   RAND_bytes(buf, sizeof(buf));
201   path += "bssl_tmp_dir." + EncodeHex(buf);
202   if (!CreateDirectoryA(path.c_str(), /*lpSecurityAttributes=*/nullptr)) {
203     perror("Could not make temporary directory");
204     return false;
205   }
206 #else
207   path += "bssl_tmp_dir.XXXXXX";
208   if (mkdtemp(path.data()) == nullptr) {
209     perror("Could not make temporary directory");
210     return false;
211   }
212 #endif
213 
214   path_ = std::move(path);
215   return true;
216 }
217 
AddFile(const std::string & filename,bssl::Span<const uint8_t> content)218 bool TemporaryDirectory::AddFile(const std::string &filename,
219                                  bssl::Span<const uint8_t> content) {
220   if (path_.empty()) {
221     return false;
222   }
223 
224   ScopedFILE file(fopen(GetFilePath(filename).c_str(), "wb"));
225   if (file == nullptr) {
226     perror("Could not open temporary file");
227     return false;
228   }
229   if (!content.empty() &&
230       fwrite(content.data(), content.size(), /*nitems=*/1, file.get()) != 1) {
231     perror("Could not write temporary file");
232     return false;
233   }
234 
235   files_.insert(filename);
236   return true;
237 }
238 
239 BSSL_NAMESPACE_END
240