Bug Summary

File:var/lib/jenkins/workspace/firefox-scan-build/toolkit/mozapps/update/common/updatecommon.cpp
Warning:line 90, column 23
Value of 'errno' was not checked and may be overwritten by function 'fopen'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name updatecommon.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -relaxed-aliasing -ffp-contract=off -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/toolkit/mozapps/update/common -fcoverage-compilation-dir=/var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/toolkit/mozapps/update/common -resource-dir /usr/lib/llvm-19/lib/clang/19 -include /var/lib/jenkins/workspace/firefox-scan-build/config/gcc_hidden.h -include /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/mozilla-config.h -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/system_wrappers -U _FORTIFY_SOURCE -D _FORTIFY_SOURCE=2 -D DEBUG=1 -D NS_NO_XPCOM -D MOZ_APP_BASENAME="Firefox" -I /var/lib/jenkins/workspace/firefox-scan-build/toolkit/mozapps/update/common -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/toolkit/mozapps/update/common -I /var/lib/jenkins/workspace/firefox-scan-build/other-licenses/nsis/Contrib/CityHash/cityhash -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include/nspr -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include/nss -D MOZILLA_CLIENT -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/backward -internal-isystem /usr/lib/llvm-19/lib/clang/19/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -Wno-error=tautological-type-limit-compare -Wno-invalid-offsetof -Wno-range-loop-analysis -Wno-deprecated-anon-enum-enum-conversion -Wno-deprecated-enum-enum-conversion -Wno-deprecated-this-capture -Wno-inline-new-delete -Wno-error=deprecated-declarations -Wno-error=array-bounds -Wno-error=free-nonheap-object -Wno-error=atomic-alignment -Wno-error=deprecated-builtins -Wno-psabi -Wno-error=builtin-macro-redefined -Wno-vla-cxx-extension -Wno-unknown-warning-option -fdeprecated-macro -ferror-limit 19 -fstrict-flex-arrays=1 -stack-protector 2 -fstack-clash-protection -ftrivial-auto-var-init=pattern -fno-rtti -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fno-sized-deallocation -fno-aligned-allocation -vectorize-loops -vectorize-slp -analyzer-checker optin.performance.Padding -analyzer-output=html -analyzer-config stable-report-filename=true -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2024-09-22-115206-3586786-1 -x c++ /var/lib/jenkins/workspace/firefox-scan-build/toolkit/mozapps/update/common/updatecommon.cpp
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5#if defined(XP_WIN)
6# include <windows.h>
7# include <winioctl.h> // for FSCTL_GET_REPARSE_POINT
8# include <shlobj.h>
9# ifndef RRF_SUBKEY_WOW6464KEY
10# define RRF_SUBKEY_WOW6464KEY 0x00010000
11# endif
12#endif
13
14#include <stdio.h>
15#include <stdarg.h>
16
17#include "updatecommon.h"
18#ifdef XP_WIN
19# include "updatehelper.h"
20# include "nsWindowsHelpers.h"
21# include "mozilla/UniquePtr.h"
22# include "mozilla/WinHeaderOnlyUtils.h"
23
24// This struct isn't in any SDK header, so this definition was copied from:
25// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/ns-ntifs-_reparse_data_buffer
26typedef struct _REPARSE_DATA_BUFFER {
27 ULONG ReparseTag;
28 USHORT ReparseDataLength;
29 USHORT Reserved;
30 union {
31 struct {
32 USHORT SubstituteNameOffset;
33 USHORT SubstituteNameLength;
34 USHORT PrintNameOffset;
35 USHORT PrintNameLength;
36 ULONG Flags;
37 WCHAR PathBuffer[1];
38 } SymbolicLinkReparseBuffer;
39 struct {
40 USHORT SubstituteNameOffset;
41 USHORT SubstituteNameLength;
42 USHORT PrintNameOffset;
43 USHORT PrintNameLength;
44 WCHAR PathBuffer[1];
45 } MountPointReparseBuffer;
46 struct {
47 UCHAR DataBuffer[1];
48 } GenericReparseBuffer;
49 } DUMMYUNIONNAME;
50} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
51#endif
52
53UpdateLog::UpdateLog() : logFP(nullptr) {}
54
55void UpdateLog::Init(NS_tchar* logFilePath) {
56 if (logFP) {
57 return;
58 }
59
60 // When the path is over the length limit disable logging by not opening the
61 // file and not setting logFP.
62 int dstFilePathLen = NS_tstrlenstrlen(logFilePath);
63 if (dstFilePathLen > 0 && dstFilePathLen < MAXPATHLEN4096 - 1) {
64 NS_tstrncpystrncpy(mDstFilePath, logFilePath, MAXPATHLEN4096);
65#if defined(XP_WIN) || defined(XP_MACOSX)
66 logFP = NS_tfopenfopen(mDstFilePath, NS_T("w")"w");
67#else
68 // On platforms that have an updates directory in the installation directory
69 // (e.g. platforms other than Windows and Mac) the update log is written to
70 // a temporary file and then to the update log file. This is needed since
71 // the installation directory is moved during a replace request. This can be
72 // removed when the platform's updates directory is located outside of the
73 // installation directory.
74 logFP = tmpfile();
75#endif
76 }
77}
78
79void UpdateLog::Finish() {
80 if (!logFP) {
1
Assuming field 'logFP' is non-null
2
Taking false branch
81 return;
82 }
83
84#if !defined(XP_WIN) && !defined(XP_MACOSX)
85 const int blockSize = 1024;
86 char buffer[blockSize];
87 fflush(logFP);
88 rewind(logFP);
3
After calling 'rewind' reading 'errno' is required to find out if the call has failed
89
90 FILE* updateLogFP = NS_tfopenfopen(mDstFilePath, NS_T("wb+")"wb+");
4
Value of 'errno' was not checked and may be overwritten by function 'fopen'
91 while (!feof(logFP)) {
92 size_t read = fread(buffer, 1, blockSize, logFP);
93 if (ferror(logFP)) {
94 fclose(logFP);
95 logFP = nullptr;
96 fclose(updateLogFP);
97 updateLogFP = nullptr;
98 return;
99 }
100
101 size_t written = 0;
102
103 while (written < read) {
104 size_t chunkWritten = fwrite(buffer, 1, read - written, updateLogFP);
105 if (chunkWritten <= 0) {
106 fclose(logFP);
107 logFP = nullptr;
108 fclose(updateLogFP);
109 updateLogFP = nullptr;
110 return;
111 }
112
113 written += chunkWritten;
114 }
115 }
116 fclose(updateLogFP);
117 updateLogFP = nullptr;
118#endif
119
120 fclose(logFP);
121 logFP = nullptr;
122}
123
124void UpdateLog::Flush() {
125 if (!logFP) {
126 return;
127 }
128
129 fflush(logFP);
130}
131
132void UpdateLog::PrintTimestampPrefix() {
133 if (!logFP) {
134 return;
135 }
136
137 time_t rawtime = time(nullptr);
138 struct tm* timeinfo = localtime(&rawtime);
139
140 if (nullptr != timeinfo) {
141 // attempt to format the time similar to rfc-3339 so that it works with
142 // sort(1). xxxx-xx-xx xx:xx:xx+xxxx -> 24 chars + 1 NUL
143 const size_t buffer_size = 25;
144 char buffer[buffer_size] = {0};
145
146 if (0 == strftime(buffer, buffer_size, "%Y-%m-%d %H:%M:%S%z", timeinfo)) {
147 buffer[0] = '\0'; // reset buffer into a defined state and try posix ts
148 if (0 > snprintf(buffer, buffer_size, "%d", (int)mktime(timeinfo))) {
149 buffer[0] = '\0'; // reset and give up
150 }
151 }
152
153 fprintf(logFP, "%s: ", buffer);
154 }
155}
156
157void UpdateLog::Printf(const char* fmt, ...) {
158 if (!logFP) {
159 return;
160 }
161
162 PrintTimestampPrefix();
163
164 va_list ap;
165 va_start(ap, fmt)__builtin_va_start(ap, fmt);
166 vfprintf(logFP, fmt, ap);
167 va_end(ap)__builtin_va_end(ap);
168
169 fprintf(logFP, "\n");
170 // When the updater crashes on Windows the log file won't be flushed and this
171 // can make it easier to debug what is going on.
172 fflush(logFP);
173}
174
175void UpdateLog::WarnPrintf(const char* fmt, ...) {
176 if (!logFP) {
177 return;
178 }
179
180 PrintTimestampPrefix();
181
182 va_list ap;
183 va_start(ap, fmt)__builtin_va_start(ap, fmt);
184 fprintf(logFP, "*** Warning: ");
185 vfprintf(logFP, fmt, ap);
186 fprintf(logFP, "***\n");
187 va_end(ap)__builtin_va_end(ap);
188 // When the updater crashes on Windows the log file won't be flushed and this
189 // can make it easier to debug what is going on.
190 fflush(logFP);
191}
192
193#ifdef XP_WIN
194/**
195 * Determine if a path contains symlinks or junctions to disallowed locations
196 *
197 * @param fullPath The full path to check.
198 * @return true if the path contains invalid links or on errors,
199 * false if the check passes and the path can be used
200 */
201bool PathContainsInvalidLinks(wchar_t* const fullPath) {
202 wchar_t pathCopy[MAXPATHLEN4096 + 1] = L"";
203 wcsncpy(pathCopy, fullPath, MAXPATHLEN4096);
204 wchar_t* remainingPath = nullptr;
205 wchar_t* nextToken = wcstok_s(pathCopy, L"\\", &remainingPath);
206 wchar_t* partialPath = nextToken;
207
208 while (nextToken) {
209 if ((GetFileAttributesW(partialPath) & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
210 nsAutoHandle h(CreateFileW(
211 partialPath, 0,
212 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
213 OPEN_EXISTING,
214 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr));
215 if (h == INVALID_HANDLE_VALUE) {
216 if (GetLastError() == ERROR_FILE_NOT_FOUND) {
217 // The path can't be an invalid link if it doesn't exist.
218 return false;
219 } else {
220 return true;
221 }
222 }
223
224 mozilla::UniquePtr<UINT8[]> byteBuffer =
225 mozilla::MakeUnique<UINT8[]>(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
226 ZeroMemory(byteBuffer.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
227 REPARSE_DATA_BUFFER* buffer = (REPARSE_DATA_BUFFER*)byteBuffer.get();
228 DWORD bytes = 0;
229 if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, nullptr, 0, buffer,
230 MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bytes, nullptr)) {
231 return true;
232 }
233
234 wchar_t* reparseTarget = nullptr;
235 switch (buffer->ReparseTag) {
236 case IO_REPARSE_TAG_MOUNT_POINT:
237 reparseTarget =
238 buffer->MountPointReparseBuffer.PathBuffer +
239 (buffer->MountPointReparseBuffer.SubstituteNameOffset /
240 sizeof(wchar_t));
241 if (buffer->MountPointReparseBuffer.SubstituteNameLength <
242 ARRAYSIZE(L"\\??\\")) {
243 return false;
244 }
245 break;
246 case IO_REPARSE_TAG_SYMLINK:
247 reparseTarget =
248 buffer->SymbolicLinkReparseBuffer.PathBuffer +
249 (buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset /
250 sizeof(wchar_t));
251 if (buffer->SymbolicLinkReparseBuffer.SubstituteNameLength <
252 ARRAYSIZE(L"\\??\\")) {
253 return false;
254 }
255 break;
256 default:
257 return true;
258 break;
259 }
260
261 if (!reparseTarget) {
262 return false;
263 }
264 if (wcsncmp(reparseTarget, L"\\??\\", ARRAYSIZE(L"\\??\\") - 1) != 0) {
265 return true;
266 }
267 }
268
269 nextToken = wcstok_s(nullptr, L"\\", &remainingPath);
270 PathAppendW(partialPath, nextToken);
271 }
272
273 return false;
274}
275
276/**
277 * Determine if a path is located within Program Files, either native or x86
278 *
279 * @param fullPath The full path to check.
280 * @return true if fullPath begins with either Program Files directory,
281 * false if it does not or if an error is encountered
282 */
283bool IsProgramFilesPath(NS_tchar* fullPath) {
284 // Make sure we don't try to compare against a short path.
285 DWORD longInstallPathChars = GetLongPathNameW(fullPath, nullptr, 0);
286 if (longInstallPathChars == 0) {
287 return false;
288 }
289 mozilla::UniquePtr<wchar_t[]> longInstallPath =
290 mozilla::MakeUnique<wchar_t[]>(longInstallPathChars);
291 if (!GetLongPathNameW(fullPath, longInstallPath.get(),
292 longInstallPathChars)) {
293 return false;
294 }
295
296 // First check for Program Files (x86).
297 {
298 PWSTR programFiles32PathRaw = nullptr;
299 // FOLDERID_ProgramFilesX86 gets native Program Files directory on a 32-bit
300 // OS or the (x86) directory on a 64-bit OS regardless of this binary's
301 // bitness.
302 if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, 0, nullptr,
303 &programFiles32PathRaw))) {
304 // That call should never fail on any supported OS version.
305 return false;
306 }
307 mozilla::UniquePtr<wchar_t, mozilla::CoTaskMemFreeDeleter>
308 programFiles32Path(programFiles32PathRaw);
309 // We need this path to have a trailing slash so our prefix test doesn't
310 // match on a different folder which happens to have a name beginning with
311 // the prefix we're looking for but then also more characters after that.
312 size_t length = wcslen(programFiles32Path.get());
313 if (length == 0) {
314 return false;
315 }
316 if (programFiles32Path.get()[length - 1] == L'\\') {
317 if (wcsnicmp(longInstallPath.get(), programFiles32Path.get(), length) ==
318 0) {
319 return true;
320 }
321 } else {
322 // Allocate space for a copy of the string along with a terminator and one
323 // extra character for the trailing backslash.
324 length += 1;
325 mozilla::UniquePtr<wchar_t[]> programFiles32PathWithSlash =
326 mozilla::MakeUnique<wchar_t[]>(length + 1);
327
328 NS_tsnprintfsnprintf(programFiles32PathWithSlash.get(), length + 1, NS_T("%s\\")"%s\\",
329 programFiles32Path.get());
330
331 if (wcsnicmp(longInstallPath.get(), programFiles32PathWithSlash.get(),
332 length) == 0) {
333 return true;
334 }
335 }
336 }
337
338 // If we didn't find (x86), check for the native Program Files.
339 {
340 // In case we're a 32-bit binary on 64-bit Windows, we now have a problem
341 // getting the right "native" Program Files path, which is that there is no
342 // FOLDERID_* value that returns that path. So we always read that one out
343 // of its canonical registry location instead. If we're on a 32-bit OS, this
344 // will be the same path that we just checked. First get the buffer size to
345 // allocate for the path.
346 DWORD length = 0;
347 if (RegGetValueW(HKEY_LOCAL_MACHINE,
348 L"Software\\Microsoft\\Windows\\CurrentVersion",
349 L"ProgramFilesDir", RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY,
350 nullptr, nullptr, &length) != ERROR_SUCCESS) {
351 return false;
352 }
353 // RegGetValue returns the length including the terminator, but it's in
354 // bytes, so convert that to characters.
355 DWORD lengthChars = (length / sizeof(wchar_t));
356 if (lengthChars <= 1) {
357 return false;
358 }
359 mozilla::UniquePtr<wchar_t[]> programFilesNativePath =
360 mozilla::MakeUnique<wchar_t[]>(lengthChars);
361
362 // Now actually read the value.
363 if (RegGetValueW(
364 HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion",
365 L"ProgramFilesDir", RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, nullptr,
366 programFilesNativePath.get(), &length) != ERROR_SUCCESS) {
367 return false;
368 }
369 size_t nativePathStrLen =
370 wcsnlen_s(programFilesNativePath.get(), lengthChars);
371 if (nativePathStrLen == 0) {
372 return false;
373 }
374
375 // As before, append a backslash if there isn't one already.
376 if (programFilesNativePath.get()[nativePathStrLen - 1] == L'\\') {
377 if (wcsnicmp(longInstallPath.get(), programFilesNativePath.get(),
378 nativePathStrLen) == 0) {
379 return true;
380 }
381 } else {
382 // Allocate space for a copy of the string along with a terminator and one
383 // extra character for the trailing backslash.
384 nativePathStrLen += 1;
385 mozilla::UniquePtr<wchar_t[]> programFilesNativePathWithSlash =
386 mozilla::MakeUnique<wchar_t[]>(nativePathStrLen + 1);
387
388 NS_tsnprintfsnprintf(programFilesNativePathWithSlash.get(), nativePathStrLen + 1,
389 NS_T("%s\\")"%s\\", programFilesNativePath.get());
390
391 if (wcsnicmp(longInstallPath.get(), programFilesNativePathWithSlash.get(),
392 nativePathStrLen) == 0) {
393 return true;
394 }
395 }
396 }
397
398 return false;
399}
400#endif
401
402/**
403 * Performs checks of a full path for validity for this application.
404 *
405 * @param origFullPath
406 * The full path to check.
407 * @return true if the path is valid for this application and false otherwise.
408 */
409bool IsValidFullPath(NS_tchar* origFullPath) {
410 // Subtract 1 from MAXPATHLEN for null termination.
411 if (NS_tstrlenstrlen(origFullPath) > MAXPATHLEN4096 - 1) {
412 // The path is longer than acceptable for this application.
413 return false;
414 }
415
416#ifdef XP_WIN
417 NS_tchar testPath[MAXPATHLEN4096] = {NS_T('\0')'\0'};
418 // GetFullPathNameW will replace / with \ which PathCanonicalizeW requires.
419 if (GetFullPathNameW(origFullPath, MAXPATHLEN4096, testPath, nullptr) == 0) {
420 // Unable to get the full name for the path (e.g. invalid path).
421 return false;
422 }
423
424 NS_tchar canonicalPath[MAXPATHLEN4096] = {NS_T('\0')'\0'};
425 if (!PathCanonicalizeW(canonicalPath, testPath)) {
426 // Path could not be canonicalized (e.g. invalid path).
427 return false;
428 }
429
430 // Check if the path passed in resolves to a differerent path.
431 if (NS_tstricmpstrcasecmp(origFullPath, canonicalPath) != 0) {
432 // Case insensitive string comparison between the supplied path and the
433 // canonical path are not equal. This will prevent directory traversal and
434 // the use of / in paths since they are converted to \.
435 return false;
436 }
437
438 NS_tstrncpystrncpy(testPath, origFullPath, MAXPATHLEN4096);
439 if (!PathStripToRootW(testPath)) {
440 // It should always be possible to strip a valid path to its root.
441 return false;
442 }
443
444 if (origFullPath[0] == NS_T('\\')'\\') {
445 // Only allow UNC server share paths.
446 if (!PathIsUNCServerShareW(testPath)) {
447 return false;
448 }
449 }
450
451 if (PathContainsInvalidLinks(canonicalPath)) {
452 return false;
453 }
454#else
455 // Only allow full paths.
456 if (origFullPath[0] != NS_T('/')'/') {
457 return false;
458 }
459
460 // The path must not traverse directories
461 if (NS_tstrstrstrstr(origFullPath, NS_T("/../")"/../") != nullptr) {
462 return false;
463 }
464
465 // The path shall not have a path traversal suffix
466 const NS_tchar invalidSuffix[] = NS_T("/..")"/..";
467 size_t pathLen = NS_tstrlenstrlen(origFullPath);
468 size_t invalidSuffixLen = NS_tstrlenstrlen(invalidSuffix);
469 if (invalidSuffixLen <= pathLen &&
470 NS_tstrncmpstrncmp(origFullPath + pathLen - invalidSuffixLen, invalidSuffix,
471 invalidSuffixLen) == 0) {
472 return false;
473 }
474#endif
475 return true;
476}