CVE-2023-37378 (C, published 2023-07-03T20:15Z)
Description: value: Nullsoft Scriptable Install System (NSIS) before 3.09 mishandles access control for an uninstaller directory.
Severity: nan (CVSS v3 base score: 5.3)
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/kichik/nsis
Files changed:
Docs/src/history.but
Source/build.cpp
Source/exehead/Main.c
Source/exehead/util.c
Commit message: Use isolated uninstaller temp directory


git-svn-id: https://svn.code.sf.net/p/nsis/code/NSIS/trunk@7388 212acab6-be3b-0410-9dea-997c60f758d6


Vulnerable code:
    cur_header->str_uninstchild = add_asciistring(_T("$TEMP\\Un_$1.exe"));
    cur_header->str_uninstcmd = add_asciistring(_T("\"$TEMP\\Un_$1.exe\" $0 _?=$INSTDIR\\"));
      int x, admin = UserIsAdminGrpMember();

      mystrcat(state_temp_dir,_T("~nsu"));
      if (admin) mystrcat(state_temp_dir,_T("A")); // Don't lock down the directory used by non-admins
      mystrcat(state_temp_dir,_T(".tmp"));

      // check if already running from uninstaller temp dir
      // this prevents recursive uninstaller calls
      if (!lstrcmpi(state_temp_dir,state_exe_directory))
        goto end;

      admin ? CreateRestrictedDirectory(state_temp_dir) : CreateNormalDirectory(state_temp_dir);
      SetCurrentDirectory(state_temp_dir);

      if (!(*state_install_directory))
        mystrcpy(state_install_directory,state_exe_directory);
      SET2CHAR(g_usrvars[1], _T("A\0"));

      for (x = 0; x < 26; x ++)
        static TCHAR buf2[NSIS_MAX_STRLEN];

        GetNSISString(buf2,g_header->str_uninstchild); // $TEMP\Un_$1.exe
        DeleteFile(buf2); // clean up after all the other ones if they are there
        if (m_Err) // not done yet
          // copy file
          if (CopyFile(state_exe_path,buf2,TRUE))
            HANDLE hProc;
            MoveFileOnReboot(buf2,NULL);
            GetNSISString(buf2,g_header->str_uninstcmd); // '"$TEMP\Un_$1.exe" $0 _?=$INSTDIR\'
            hProc=myCreateProcess(buf2);
              // success
              m_Err = 0;
        (*(((NSIS_STRING *)g_usrvars)[1]))++;

#ifdef NSIS_SUPPORT_MOVEONREBOOT
      MoveFileOnReboot(state_temp_dir,NULL);
#endif

  const SECURITY_INFORMATION si = OWNER_SECURITY_INFORMATION|GROUP_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION|PROTECTED_DACL_SECURITY_INFORMATION;
  DWORD ec = CreateDirectory(path, &sa) ? ERROR_SUCCESS : GetLastError();
  if (ERROR_ALREADY_EXISTS == ec)
    ec = SetFileSecurity(path, si, &sd) ? ERROR_SUCCESS : GetLastError();
  return ec;

Fixed code:
\S2{} Major Changes

\b Use isolated uninstaller directory for every instance

    cur_header->str_uninstchild = add_asciistring(_T("$TEMP\\Un.exe"));
    cur_header->str_uninstcmd = add_asciistring(_T("\"$TEMP\\Un.exe\" $0 _?=$INSTDIR\\"));
      int admin = UserIsAdminGrpMember();
      WORD tries; // 0xfffe attempts or bust
      size_t cchtmpslash = mystrlen(state_temp_dir);
      LPTSTR unexe = g_usrvars[5], unexecmd = g_usrvars[6];
      if (!(*state_install_directory))
        mystrcpy(state_install_directory, state_exe_directory);
      }
      for (tries = 0; ++tries != 0;)
      {
        DWORD retry = 0, ec;
retry_un_dir:
        wsprintf(state_temp_dir + cchtmpslash, _T("~nsu%X.tmp"), tries);
        GetNSISString(unexe, g_header->str_uninstchild); // '$TEMP\Un.exe'
        if (admin)
        {
          ec = CreateRestrictedDirectory(state_temp_dir);
        }
        else
        {
          ec = CreateNormalDirectory(state_temp_dir);
        }
        if (ec)
          // Delete previous uninstaller
          if (DeleteFile(unexe))
          {
            myDelete(state_temp_dir, DEL_DIR|DEL_RECURSE);
            if (!retry++) goto retry_un_dir;
          }
        }
        else
        {
          HANDLE hProc;
          SetCurrentDirectory(state_temp_dir);
#ifdef NSIS_SUPPORT_MOVEONREBOOT
          MoveFileOnReboot(state_temp_dir, NULL);
#endif
          if (CopyFile(state_exe_path, unexe, TRUE))
            MoveFileOnReboot(unexe, NULL);
            GetNSISString(unexecmd, g_header->str_uninstcmd); // '"$TEMP\Un.exe" $0 _?=$INSTDIR\'
            hProc = myCreateProcess(unexecmd);
              m_Err = 0; // Success
            }
            else if (!retry++ && !file_exists(unexe))
            {
              // Another instance deleted us between CopyFile and CreateProcess
              goto retry_un_dir;
          break; // We called CreateProcess; success or failure, we are done.
  return CreateDirectory(path, &sa) ? ERROR_SUCCESS : GetLastError();