Harden installer activation against ACC file locks
This commit is contained in:
@@ -29,6 +29,9 @@ namespace BizTalkPlatformManagementTool.Tests
|
||||
Run("PackageManifestRejectsTampering", PackageManifestRejectsTampering);
|
||||
Run("PackageManifestRejectsUndeclaredAndTraversalFiles", PackageManifestRejectsUndeclaredAndTraversalFiles);
|
||||
Run("InstallerActivatesValidatedPayload", InstallerActivatesValidatedPayload);
|
||||
Run("InstallerRetriesTransientActivationMove", InstallerRetriesTransientActivationMove);
|
||||
Run("InstallerUsesVerifiedCopyFallbackForNewInstall", InstallerUsesVerifiedCopyFallbackForNewInstall);
|
||||
Run("InstallerStopsAfterBoundedUpdateMoveRetries", InstallerStopsAfterBoundedUpdateMoveRetries);
|
||||
Run("InstallerDoesNotMutateOnStagingFailure", InstallerDoesNotMutateOnStagingFailure);
|
||||
Run("InstallerRollsBackFailedActivatedSelfTest", InstallerRollsBackFailedActivatedSelfTest);
|
||||
Run("InstallerUninstallRemovesProgramDirectory", InstallerUninstallRemovesProgramDirectory);
|
||||
@@ -147,6 +150,118 @@ namespace BizTalkPlatformManagementTool.Tests
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Prüft die erfolgreiche Aktivierung nach einer kurzzeitigen Rename-Sperre.</summary>
|
||||
private static void InstallerRetriesTransientActivationMove()
|
||||
{
|
||||
InTemp(directory =>
|
||||
{
|
||||
var package = CreatePackage(directory, "new");
|
||||
var install = Path.Combine(directory, "install");
|
||||
var data = Path.Combine(directory, "data");
|
||||
var moveCalls = 0;
|
||||
var delays = new List<int>();
|
||||
var engine = new InstallerEngine(
|
||||
package,
|
||||
install,
|
||||
data,
|
||||
false,
|
||||
path => true,
|
||||
(source, target) =>
|
||||
{
|
||||
moveCalls++;
|
||||
if (moveCalls == 1) throw new IOException("simulated transient scanner lock");
|
||||
Directory.Move(source, target);
|
||||
},
|
||||
delays.Add);
|
||||
|
||||
engine.Install(false, null);
|
||||
|
||||
Assert(moveCalls == 2, "transient activation move was not retried exactly once");
|
||||
Assert(delays.SequenceEqual(new[] { 250 }), "unexpected retry delay for transient activation move");
|
||||
Assert(File.ReadAllText(Path.Combine(install, InstallerEngine.ApplicationExeName)) == "new", "payload was not activated after retry");
|
||||
var log = File.ReadAllText(Directory.GetFiles(Path.Combine(data, "InstallerLogs"), "setup-*.log").Single());
|
||||
Assert(log.Contains("event=directory_move_retry role=activate failed_attempt=1"), "transient move retry was not diagnosed");
|
||||
Assert(log.Contains("event=directory_move_recovered role=activate attempt=2"), "move recovery was not diagnosed");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Prüft den verifizierten Kopierfallback einer durchgehend gesperrten Neuinstallation.</summary>
|
||||
private static void InstallerUsesVerifiedCopyFallbackForNewInstall()
|
||||
{
|
||||
InTemp(directory =>
|
||||
{
|
||||
var package = CreatePackage(directory, "new");
|
||||
var install = Path.Combine(directory, "install");
|
||||
var data = Path.Combine(directory, "data");
|
||||
var moveCalls = 0;
|
||||
var delays = new List<int>();
|
||||
var engine = new InstallerEngine(
|
||||
package,
|
||||
install,
|
||||
data,
|
||||
false,
|
||||
path => true,
|
||||
(source, target) =>
|
||||
{
|
||||
moveCalls++;
|
||||
throw new UnauthorizedAccessException("simulated permanent policy denial");
|
||||
},
|
||||
delays.Add);
|
||||
|
||||
engine.Install(false, null);
|
||||
|
||||
Assert(moveCalls == 8, "permanent move failure did not stop after eight attempts");
|
||||
Assert(delays.SequenceEqual(new[] { 250, 500, 1000, 2000, 3000, 5000, 8000 }), "bounded retry schedule changed unexpectedly");
|
||||
Assert(File.ReadAllText(Path.Combine(install, InstallerEngine.ApplicationExeName)) == "new", "verified copy fallback did not activate payload");
|
||||
Assert(!Directory.GetDirectories(directory, "install.staging.*").Any(), "staging remained after copy fallback");
|
||||
var log = File.ReadAllText(Directory.GetFiles(Path.Combine(data, "InstallerLogs"), "setup-*.log").Single());
|
||||
Assert(log.Contains("event=directory_move_retry role=activate failed_attempt=7"), "final scheduled retry was not diagnosed");
|
||||
Assert(log.Contains("exception_type=System.UnauthorizedAccessException"), "ACL failure type missing from retry diagnostics");
|
||||
Assert(log.Contains("event=activation_fallback_complete method=verified_copy"), "copy fallback completion was not diagnosed");
|
||||
Assert(log.Contains("activation_method=verified_copy_fallback"), "copy fallback missing from setup summary");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Prüft, dass ein Update bei dauerhaft gesperrtem Backup atomar und unverändert abbricht.</summary>
|
||||
private static void InstallerStopsAfterBoundedUpdateMoveRetries()
|
||||
{
|
||||
InTemp(directory =>
|
||||
{
|
||||
var package = CreatePackage(directory, "new");
|
||||
var install = Path.Combine(directory, "install");
|
||||
Directory.CreateDirectory(install);
|
||||
File.WriteAllText(Path.Combine(install, InstallerEngine.ApplicationExeName), "old");
|
||||
File.WriteAllText(Path.Combine(install, InstallerEngine.ApplicationExeName + ".config"), "old-config");
|
||||
var data = Path.Combine(directory, "data");
|
||||
var moveCalls = 0;
|
||||
var delays = new List<int>();
|
||||
var engine = new InstallerEngine(
|
||||
package,
|
||||
install,
|
||||
data,
|
||||
false,
|
||||
path => true,
|
||||
(source, target) =>
|
||||
{
|
||||
moveCalls++;
|
||||
throw new UnauthorizedAccessException("simulated permanent update policy denial");
|
||||
},
|
||||
delays.Add);
|
||||
|
||||
var exception = Capture<InvalidOperationException>(() => engine.Install(false, null));
|
||||
|
||||
Assert(moveCalls == 8, "permanent update move failure did not stop after eight attempts");
|
||||
Assert(delays.SequenceEqual(new[] { 250, 500, 1000, 2000, 3000, 5000, 8000 }), "bounded update retry schedule changed unexpectedly");
|
||||
Assert(File.ReadAllText(Path.Combine(install, InstallerEngine.ApplicationExeName)) == "old", "failed update modified the active installation");
|
||||
Assert(!Directory.GetDirectories(directory, "install.staging.*").Any(), "staging remained after permanent update failure");
|
||||
Assert(exception.Message.Contains("Fehlercode=SETUP-ACTIVATION"), "activation error code missing after update retries");
|
||||
Assert(exception.Message.Contains("Kein Rollback erforderlich"), "pre-mutation update denial reported a rollback");
|
||||
var log = File.ReadAllText(Directory.GetFiles(Path.Combine(data, "InstallerLogs"), "setup-*.log").Single());
|
||||
Assert(log.Contains("event=directory_move_retry role=backup failed_attempt=7"), "final update retry was not diagnosed");
|
||||
Assert(!log.Contains("event=activation_fallback_started"), "update incorrectly used the new-install copy fallback");
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Prüft die Ablehnung nicht deklarierter Dateien und ausbrechender Manifestpfade.</summary>
|
||||
private static void PackageManifestRejectsUndeclaredAndTraversalFiles()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user