Pārlūkot izejas kodu

Uninstaller: copy second phase executable into a safe location

Regular users' `%TMP%` directory might be a safe location because other
users cannot write into it. But SYSTEM's `%TMP%` is set to
`C:\Windows\Temp` by default, which is typically world-writable.

This opens the uninstaller up to DLL hijacking when run under the SYSTEM
user (note: this is different from merely running the uninstaller as an
admin user with elevated privileges).

Let's just do the same as we do in the installer: instead of copying or
extracting the executable into `%TMP%` directly, let's just create a
subdirectory and copy/extract into that. With this, we can guarantee
that no malicious `.dll` has been placed there.

Note: Since the second phase executable cannot delete itself, we need to
use a non-random name (only admins can run `RestartReplace()`
successfully, that function will fail if other users call it, therefore
we want at least that the temporary directory is reused the next time
an uninstaller is run). We still will want to ensure that the directory
has been newly created; To that end, we first try to delete any file or
diretory that is in the way, and only then create it.

Signed-off-by: Johannes Schindelin <[email protected]>
Johannes Schindelin 3 gadi atpakaļ
vecāks
revīzija
fa2c741fa5
2 mainītis faili ar 30 papildinājumiem un 26 dzēšanām
  1. 20 20
      Projects/InstFunc.pas
  2. 10 6
      Projects/Uninstall.pas

+ 20 - 20
Projects/InstFunc.pas

@@ -64,7 +64,7 @@ function DetermineDefaultLanguage(const GetLanguageEntryProc: TGetLanguageEntryP
   var ResultIndex: Integer): TDetermineDefaultLanguageResult;
 procedure EnumFileReplaceOperationsFilenames(const EnumFunc: TEnumFROFilenamesProc;
   Param: Pointer);
-function GenerateNonRandomUniqueFilename(Path: String; var Filename: String): Boolean;
+function GenerateNonRandomUniqueTempDir(Path: String; var TempDir: String): Boolean;
 function GenerateUniqueName(const DisableFsRedir: Boolean; Path: String;
   const Extension: String): String;
 function GetComputerNameString: String;
@@ -210,20 +210,18 @@ begin
   Result := Filename;
 end;
 
-function GenerateNonRandomUniqueFilename(Path: String; var Filename: String): Boolean;
-{ Returns True if it overwrote an existing file. }
+function GenerateNonRandomUniqueTempDir(Path: String; var TempDir: String): Boolean;
+{ Creates a new temporary directory with a non-random name. Returns True if an
+  existing directory was re-created. }
 var
   Rand, RandOrig: Longint;
-  F: THandle;
-  Success: Boolean;
-  FN: String;
+  ErrorCode: DWORD;
 begin
   Path := AddBackslash(Path);
   RandOrig := $123456;
   Rand := RandOrig;
-  Success := False;
-  Result := False;
   repeat
+    Result := False;
     Inc(Rand);
     if Rand > $1FFFFFF then Rand := 0;
     if Rand = RandOrig then
@@ -232,18 +230,20 @@ begin
       raise Exception.Create(FmtSetupMessage1(msgErrorTooManyFilesInDir,
         RemoveBackslashUnlessRoot(Path)));
     { Generate a random name }
-    FN := Path + '_iu' + IntToBase32(Rand) + '.tmp';
-    if DirExists(FN) then Continue;
-    Success := True;
-    Result := NewFileExists(FN);
-    if Result then begin
-      F := CreateFile(PChar(FN), GENERIC_READ or GENERIC_WRITE, 0,
-        nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
-      Success := F <> INVALID_HANDLE_VALUE;
-      if Success then CloseHandle(F);
-    end;
-  until Success;
-  Filename := FN;
+    TempDir := Path + 'iu-' + IntToBase32(Rand) + '.tmp';
+    if DirExists(TempDir) then begin
+      if not DeleteDirTree(TempDir) then continue;
+      Result := True;
+    end else if NewFileExists(TempDir) then
+      if not DeleteFile(TempDir) then continue;
+
+    if CreateDirectory(PChar(TempDir), nil) then break;
+    ErrorCode := GetLastError;
+    if ErrorCode <> ERROR_ALREADY_EXISTS then
+      raise Exception.Create(FmtSetupMessage(msgLastErrorMessage,
+        [FmtSetupMessage1(msgErrorCreatingDir, TempDir), IntToStr(ErrorCode),
+         Win32ErrorString(ErrorCode)]));
+  until False; // continue until a new directory was created
 end;
 
 function CreateTempDir: String;

+ 10 - 6
Projects/Uninstall.pas

@@ -369,17 +369,21 @@ end;
 
 procedure RunFirstPhase;
 var
-  TempFile: String;
+  TempDir, TempFile: String;
+  TempDirExisted: Boolean;
   Wnd: HWND;
   ProcessHandle: THandle;
 begin
-  { Copy self to TEMP directory with a name like _iu14D2N.tmp. The
-    actual uninstallation process must be done from somewhere outside
-    the application directory since EXE's can't delete themselves while
-    they are running. }
-  if not GenerateNonRandomUniqueFilename(GetTempDir, TempFile) then
+  { Copy self to a subdirectory of the TEMP directory with a name like
+    _iu14D2N.tmp. The actual uninstallation process must be done from
+    somewhere outside the application directory since EXE's can't delete
+    themselves while they are running. }
+  TempDirExisted := GenerateNonRandomUniqueTempDir(GetTempDir, TempDir);
+  TempFile := AddBackslash(TempDir) + '_unins.tmp';
+  if not TempDirExisted then
     try
       RestartReplace(False, TempFile, '');
+      RestartReplace(False, TempDir, '');
     except
       { ignore exceptions }
     end;