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-20/lib/clang/20 -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 _GLIBCXX_ASSERTIONS -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-20/lib/clang/20/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-2025-01-20-090804-167946-1 -x c++ /var/lib/jenkins/workspace/firefox-scan-build/toolkit/mozapps/update/common/updatecommon.cpp
| 1 | |
| 2 | |
| 3 | |
| 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 | |
| 25 | |
| 26 | typedef 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 | |
| 53 | UpdateLog::UpdateLog() : logFP(nullptr) {} |
| 54 | |
| 55 | void UpdateLog::Init(NS_tchar* logFilePath) { |
| 56 | if (logFP) { |
| 57 | return; |
| 58 | } |
| 59 | |
| 60 | |
| 61 | |
| 62 | int dstFilePathLen = NS_tstrlen(logFilePath); |
| 63 | if (dstFilePathLen > 0 && dstFilePathLen < MAXPATHLEN - 1) { |
| 64 | NS_tstrncpy(mDstFilePath, logFilePath, MAXPATHLEN); |
| 65 | #if defined(XP_WIN) || defined(XP_MACOSX) |
| 66 | logFP = NS_tfopen(mDstFilePath, NS_T("w")); |
| 67 | #else |
| 68 | |
| 69 | |
| 70 | |
| 71 | |
| 72 | |
| 73 | |
| 74 | logFP = tmpfile(); |
| 75 | #endif |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | void UpdateLog::Finish() { |
| 80 | if (!logFP) { |
| 1 | Assuming field 'logFP' is non-null | |
|
| |
| 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_tfopen(mDstFilePath, NS_T("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 | |
| 124 | void UpdateLog::Flush() { |
| 125 | if (!logFP) { |
| 126 | return; |
| 127 | } |
| 128 | |
| 129 | fflush(logFP); |
| 130 | } |
| 131 | |
| 132 | void 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 | |
| 142 | |
| 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'; |
| 148 | if (0 > snprintf(buffer, buffer_size, "%d", (int)mktime(timeinfo))) { |
| 149 | buffer[0] = '\0'; |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | fprintf(logFP, "%s: ", buffer); |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | void UpdateLog::Printf(const char* fmt, ...) { |
| 158 | if (!logFP) { |
| 159 | return; |
| 160 | } |
| 161 | |
| 162 | PrintTimestampPrefix(); |
| 163 | |
| 164 | va_list ap; |
| 165 | va_start(ap, fmt); |
| 166 | vfprintf(logFP, fmt, ap); |
| 167 | va_end(ap); |
| 168 | |
| 169 | fprintf(logFP, "\n"); |
| 170 | |
| 171 | |
| 172 | fflush(logFP); |
| 173 | } |
| 174 | |
| 175 | void UpdateLog::WarnPrintf(const char* fmt, ...) { |
| 176 | if (!logFP) { |
| 177 | return; |
| 178 | } |
| 179 | |
| 180 | PrintTimestampPrefix(); |
| 181 | |
| 182 | va_list ap; |
| 183 | va_start(ap, fmt); |
| 184 | fprintf(logFP, "*** Warning: "); |
| 185 | vfprintf(logFP, fmt, ap); |
| 186 | fprintf(logFP, "***\n"); |
| 187 | va_end(ap); |
| 188 | |
| 189 | |
| 190 | fflush(logFP); |
| 191 | } |
| 192 | |
| 193 | #ifdef XP_WIN |
| 194 | |
| 195 | |
| 196 | |
| 197 | |
| 198 | |
| 199 | |
| 200 | |
| 201 | bool PathContainsInvalidLinks(wchar_t* const fullPath) { |
| 202 | wchar_t pathCopy[MAXPATHLEN + 1] = L""; |
| 203 | wcsncpy(pathCopy, fullPath, MAXPATHLEN); |
| 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 | |
| 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 | |
| 278 | |
| 279 | |
| 280 | |
| 281 | |
| 282 | |
| 283 | bool IsProgramFilesPath(NS_tchar* fullPath) { |
| 284 | |
| 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 | |
| 297 | { |
| 298 | PWSTR programFiles32PathRaw = nullptr; |
| 299 | |
| 300 | |
| 301 | |
| 302 | if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, 0, nullptr, |
| 303 | &programFiles32PathRaw))) { |
| 304 | |
| 305 | return false; |
| 306 | } |
| 307 | mozilla::UniquePtr<wchar_t, mozilla::CoTaskMemFreeDeleter> |
| 308 | programFiles32Path(programFiles32PathRaw); |
| 309 | |
| 310 | |
| 311 | |
| 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 | |
| 323 | |
| 324 | length += 1; |
| 325 | mozilla::UniquePtr<wchar_t[]> programFiles32PathWithSlash = |
| 326 | mozilla::MakeUnique<wchar_t[]>(length + 1); |
| 327 | |
| 328 | NS_tsnprintf(programFiles32PathWithSlash.get(), length + 1, NS_T("%s\\"), |
| 329 | programFiles32Path.get()); |
| 330 | |
| 331 | if (wcsnicmp(longInstallPath.get(), programFiles32PathWithSlash.get(), |
| 332 | length) == 0) { |
| 333 | return true; |
| 334 | } |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | |
| 339 | { |
| 340 | |
| 341 | |
| 342 | |
| 343 | |
| 344 | |
| 345 | |
| 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 | |
| 354 | |
| 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 | |
| 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 | |
| 376 | if (programFilesNativePath.get()[nativePathStrLen - 1] == L'\\') { |
| 377 | if (wcsnicmp(longInstallPath.get(), programFilesNativePath.get(), |
| 378 | nativePathStrLen) == 0) { |
| 379 | return true; |
| 380 | } |
| 381 | } else { |
| 382 | |
| 383 | |
| 384 | nativePathStrLen += 1; |
| 385 | mozilla::UniquePtr<wchar_t[]> programFilesNativePathWithSlash = |
| 386 | mozilla::MakeUnique<wchar_t[]>(nativePathStrLen + 1); |
| 387 | |
| 388 | NS_tsnprintf(programFilesNativePathWithSlash.get(), nativePathStrLen + 1, |
| 389 | NS_T("%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 | |
| 404 | |
| 405 | |
| 406 | |
| 407 | |
| 408 | |
| 409 | bool IsValidFullPath(NS_tchar* origFullPath) { |
| 410 | |
| 411 | if (NS_tstrlen(origFullPath) > MAXPATHLEN - 1) { |
| 412 | |
| 413 | return false; |
| 414 | } |
| 415 | |
| 416 | #ifdef XP_WIN |
| 417 | NS_tchar testPath[MAXPATHLEN] = {NS_T('\0')}; |
| 418 | |
| 419 | if (GetFullPathNameW(origFullPath, MAXPATHLEN, testPath, nullptr) == 0) { |
| 420 | |
| 421 | return false; |
| 422 | } |
| 423 | |
| 424 | NS_tchar canonicalPath[MAXPATHLEN] = {NS_T('\0')}; |
| 425 | if (!PathCanonicalizeW(canonicalPath, testPath)) { |
| 426 | |
| 427 | return false; |
| 428 | } |
| 429 | |
| 430 | |
| 431 | if (NS_tstricmp(origFullPath, canonicalPath) != 0) { |
| 432 | |
| 433 | |
| 434 | |
| 435 | return false; |
| 436 | } |
| 437 | |
| 438 | NS_tstrncpy(testPath, origFullPath, MAXPATHLEN); |
| 439 | if (!PathStripToRootW(testPath)) { |
| 440 | |
| 441 | return false; |
| 442 | } |
| 443 | |
| 444 | if (origFullPath[0] == NS_T('\\')) { |
| 445 | |
| 446 | if (!PathIsUNCServerShareW(testPath)) { |
| 447 | return false; |
| 448 | } |
| 449 | } |
| 450 | |
| 451 | if (PathContainsInvalidLinks(canonicalPath)) { |
| 452 | return false; |
| 453 | } |
| 454 | #else |
| 455 | |
| 456 | if (origFullPath[0] != NS_T('/')) { |
| 457 | return false; |
| 458 | } |
| 459 | |
| 460 | |
| 461 | if (NS_tstrstr(origFullPath, NS_T("/../")) != nullptr) { |
| 462 | return false; |
| 463 | } |
| 464 | |
| 465 | |
| 466 | const NS_tchar invalidSuffix[] = NS_T("/.."); |
| 467 | size_t pathLen = NS_tstrlen(origFullPath); |
| 468 | size_t invalidSuffixLen = NS_tstrlen(invalidSuffix); |
| 469 | if (invalidSuffixLen <= pathLen && |
| 470 | NS_tstrncmp(origFullPath + pathLen - invalidSuffixLen, invalidSuffix, |
| 471 | invalidSuffixLen) == 0) { |
| 472 | return false; |
| 473 | } |
| 474 | #endif |
| 475 | return true; |
| 476 | } |