CVE-2023-37378 (published 2023-07-03T20:15Z)

Nullsoft Scriptable Install System (NSIS) before 3.09 mishandles access control for an uninstaller directory.

CVSS v3 base score: 5.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/kichik/nsis
commit_details: repo_url: https://github.com/kichik/nsis
author: Anders
author_date: 2023-05-21 16:15:16+00:00
msg: Use isolated uninstaller temp directory


git-svn-id: https://svn.code.sf.net/p/nsis/code/NSIS/trunk@7388 212acab6-be3b-0410-9dea-997c60f758d6
repository: repo_url: https://github.com/kichik/nsis
repo_name: kichik/nsis
description: ***This is just a mirror of https://sf.net/projects/nsis -- please report issues there*** NSIS (Nullsoft Scriptable Install System) is a professional open source system to create Windows installers. It is designed to be as small and flexible as possible and is therefore very suitable for internet distribution.
repo_language: C++
owner: kichik
file_changes: filename: history.but
change_type: ModificationType.MODIFY
diff: @@ -10,6 +10,10 @@ Released on ????, 20??
 
 \S1{v3.09-cl} Changelog
 
+\S2{} Major Changes
+
+\b Use isolated uninstaller directory for every instance
+
 \S2{} Minor Changes
 
 \b Added \R{assert}{!assert}

programming_language: TeX
filename: build.cpp
change_type: ModificationType.MODIFY
diff: @@ -2326,8 +2326,8 @@ void CEXEBuild::AddStandardStrings()
 #ifdef NSIS_CONFIG_UNINSTALL_SUPPORT
   if (uninstall_mode)
   {
-    cur_header->str_uninstchild = add_asciistring(_T("$TEMP\\Un_$1.exe"));
-    cur_header->str_uninstcmd = add_asciistring(_T("\"$TEMP\\Un_$1.exe\" $0 _?=$INSTDIR\\"));
+    cur_header->str_uninstchild = add_asciistring(_T("$TEMP\\Un.exe"));
+    cur_header->str_uninstcmd = add_asciistring(_T("\"$TEMP\\Un.exe\" $0 _?=$INSTDIR\\"));
   }
 #endif//NSIS_CONFIG_UNINSTALL_SUPPORT
 #ifdef NSIS_SUPPORT_MOVEONREBOOT

programming_language: C++
filename: Main.c
change_type: ModificationType.MODIFY
diff: @@ -348,60 +348,69 @@ EXTERN_C void NSISWinMainNOCRT()
     }
     else
     {
-      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);
+      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];
 
       mystrcpy(g_usrvars[0], realcmds);
-      SET2CHAR(g_usrvars[1], _T("A\0"));
-
-      for (x = 0; x < 26; x ++)
+      if (!(*state_install_directory))
       {
-        static TCHAR buf2[NSIS_MAX_STRLEN];
-
-        GetNSISString(buf2,g_header->str_uninstchild); // $TEMP\Un_$1.exe
+        mystrcpy(state_install_directory, state_exe_directory);
+      }
 
-        DeleteFile(buf2); // clean up after all the other ones if they are there
+      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 (m_Err) // not done yet
+        if (ec)
         {
-          // copy file
-          if (CopyFile(state_exe_path,buf2,TRUE))
+          // 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))
           {
-            HANDLE hProc;
 #ifdef NSIS_SUPPORT_MOVEONREBOOT
-            MoveFileOnReboot(buf2,NULL);
+            MoveFileOnReboot(unexe, NULL);
 #endif
-            GetNSISString(buf2,g_header->str_uninstcmd); // '"$TEMP\Un_$1.exe" $0 _?=$INSTDIR\'
-            hProc=myCreateProcess(buf2);
+            GetNSISString(unexecmd, g_header->str_uninstcmd); // '"$TEMP\Un.exe" $0 _?=$INSTDIR\'
+            hProc = myCreateProcess(unexecmd);
             if (hProc)
             {
               CloseHandle(hProc);
-              // success
-              m_Err = 0;
+              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.
         }
-        (*(((NSIS_STRING *)g_usrvars)[1]))++;
       }
-
-#ifdef NSIS_SUPPORT_MOVEONREBOOT
-      MoveFileOnReboot(state_temp_dir,NULL);
-#endif
-
       goto end;
     }
   }

programming_language: C++
filename: util.c
change_type: ModificationType.MODIFY
diff: @@ -68,14 +68,10 @@ const UINT32 g_restrictedacl[] = {
 
 DWORD NSISCALL CreateRestrictedDirectory(LPCTSTR path)
 {
-  const SECURITY_INFORMATION si = OWNER_SECURITY_INFORMATION|GROUP_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION|PROTECTED_DACL_SECURITY_INFORMATION;
   PSID admingrpsid = GetAdminGrpSid();
   SECURITY_DESCRIPTOR sd = { 1, 0, SE_DACL_PRESENT, admingrpsid, admingrpsid, NULL, GetAdminGrpAcl() };
   SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), &sd, FALSE };
-  DWORD ec = CreateDirectory(path, &sa) ? ERROR_SUCCESS : GetLastError();
-  if (ERROR_ALREADY_EXISTS == ec)
-    ec = SetFileSecurity(path, si, &sd) ? ERROR_SUCCESS : GetLastError();
-  return ec;
+  return CreateDirectory(path, &sa) ? ERROR_SUCCESS : GetLastError();
 }
 DWORD NSISCALL CreateNormalDirectory(LPCTSTR path)
 {

programming_language: C++
repo_url: https://github.com/kichik/nsis
commit_details: repo_url: https://github.com/kichik/nsis
author: Anders
author_date: 2023-06-21 23:38:48+00:00
msg: Don't allow everyone to delete the uninstaller directory


git-svn-id: https://svn.code.sf.net/p/nsis/code/NSIS/trunk@7396 212acab6-be3b-0410-9dea-997c60f758d6
repository: repo_url: https://github.com/kichik/nsis
repo_name: kichik/nsis
description: ***This is just a mirror of https://sf.net/projects/nsis -- please report issues there*** NSIS (Nullsoft Scriptable Install System) is a professional open source system to create Windows installers. It is designed to be as small and flexible as possible and is therefore very suitable for internet distribution.
repo_language: C++
owner: kichik
file_changes: filename: util.c
change_type: ModificationType.MODIFY
diff: @@ -62,7 +62,7 @@ const UINT32 g_restrictedacl[] = {
   0x10000000, // ACCESS_ALLOWED_ACE:ACCESS_MASK: GENERIC_ALL
   0x00000201, 0x05000000, 0x00000020, 0x00000220, // ACCESS_ALLOWED_ACE:SID (BUILTIN\Administrators) NOTE: GetAdminGrpSid() relies on this being the first SID in the ACL
   0x00140300, // ACCESS_ALLOWED_ACE:ACE_HEADER (ACCESS_ALLOWED_ACE_TYPE, CONTAINER_INHERIT_ACE|OBJECT_INHERIT_ACE)
-  0x00130041, // ACCESS_ALLOWED_ACE:ACCESS_MASK: DELETE|READ_CONTROL|SYNCHRONIZE|FILE_DELETE_CHILD|FILE_LIST_DIRECTORY
+  0x001200c1, // ACCESS_ALLOWED_ACE:ACCESS_MASK: SYNCHRONIZE|READ_CONTROL|FILE_LIST_DIRECTORY|FILE_DELETE_CHILD|FILE_READ_ATTRIBUTES
   0x00000101, 0x01000000, 0x00000000 // ACCESS_ALLOWED_ACE:SID (WORLD\Everyone)
 };
 

programming_language: C++
repo_url: https://github.com/kichik/nsis
commit_details: repo_url: https://github.com/kichik/nsis
author: Anders
author_date: 2023-06-03 15:10:54+00:00
msg: Don't delete old uninstaller if it points somewhere else


git-svn-id: https://svn.code.sf.net/p/nsis/code/NSIS/trunk@7394 212acab6-be3b-0410-9dea-997c60f758d6
repository: repo_url: https://github.com/kichik/nsis
repo_name: kichik/nsis
description: ***This is just a mirror of https://sf.net/projects/nsis -- please report issues there*** NSIS (Nullsoft Scriptable Install System) is a professional open source system to create Windows installers. It is designed to be as small and flexible as possible and is therefore very suitable for internet distribution.
repo_language: C++
owner: kichik
file_changes: filename: Main.c
change_type: ModificationType.MODIFY
diff: @@ -376,10 +376,10 @@ retry_un_dir:
 
         if (ec)
         {
-          // Delete previous uninstaller
-          if (DeleteFile(unexe))
+          // Delete previous uninstaller (if it is safe to do so)
+          if (!(GetFileAttributes(unexe) & FILE_ATTRIBUTE_REPARSE_POINT) && DeleteFile(unexe))
           {
-            myDelete(state_temp_dir, DEL_DIR|DEL_RECURSE);
+            myDelete(state_temp_dir, DEL_DIR);
             if (!retry++) goto retry_un_dir;
           }
         }

programming_language: C++