fix(launcher): harden Campaign LA11 gate evidence

This commit is contained in:
Erik 2026-08-15 01:08:41 +02:00
parent 134edabed2
commit accd01a008
16 changed files with 1820 additions and 210 deletions

View file

@ -295,29 +295,228 @@ public sealed class LauncherSelfUpdateManagerTests : IDisposable
Assert.Equal(publicArguments, ordinary.RemainingArguments);
SelfUpdateStartupResult deferred = await LauncherSelfUpdateBootstrap.HandleAsync(
[LauncherSelfUpdateBootstrap.DeferredArgument, .. publicArguments],
["--acdream-self-update-deferred-v1", .. publicArguments],
harness.Manager,
harness.Target,
harness.LauncherPath);
Assert.False(deferred.ShouldExit);
Assert.Equal(publicArguments, deferred.RemainingArguments);
Assert.True(deferred.ShouldExit);
Assert.Equal(64, deferred.ExitCode);
Assert.Empty(deferred.RemainingArguments);
_ = await harness.StageAsync();
SelfUpdatePlan applied = await harness.Manager.ApplyPendingAsync(harness.Target);
SelfUpdateStartupResult confirmation = await LauncherSelfUpdateBootstrap.HandleAsync(
[
LauncherSelfUpdateBootstrap.ConfirmArgument,
applied.TransactionId,
.. publicArguments,
],
SelfUpdateStartupResult confirmation;
using (UpdateSessionBarrier.ExclusiveLease helperLease =
harness.Manager.Barrier.AcquireExclusive())
{
confirmation = await LauncherSelfUpdateBootstrap.HandleAsync(
[
LauncherSelfUpdateBootstrap.ConfirmArgument,
applied.TransactionId,
.. publicArguments,
],
harness.Manager,
harness.Target,
harness.LauncherPath);
}
Assert.False(confirmation.ShouldExit);
Assert.Equal(publicArguments, confirmation.RemainingArguments);
Assert.True(File.Exists(harness.Manager.PendingPlanPath));
Assert.True(harness.Manager.IsConfirmed(applied.TransactionId));
await harness.Manager.CompleteConfirmedAsync(applied.TransactionId, harness.Target);
Assert.False(File.Exists(harness.Manager.PendingPlanPath));
}
[Fact]
public async Task ContendedOrdinaryStartupAllowsOnlyNoPlanOrValidatedStagedPlan()
{
using var harness = new Harness(_root);
using (UpdateSessionBarrier.SessionLease session =
harness.Manager.Barrier.AcquireSession())
{
SelfUpdateStartupResult empty = await LauncherSelfUpdateBootstrap.HandleAsync(
["ordinary"],
harness.Manager,
harness.Target,
harness.LauncherPath);
Assert.False(empty.ShouldExit);
}
_ = await harness.StageAsync();
using (UpdateSessionBarrier.SessionLease session =
harness.Manager.Barrier.AcquireSession())
{
SelfUpdateStartupResult staged = await LauncherSelfUpdateBootstrap.HandleAsync(
["ordinary"],
harness.Manager,
harness.Target,
harness.LauncherPath);
Assert.False(staged.ShouldExit);
}
SelfUpdatePlan awaiting = await harness.Manager.ApplyPendingAsync(harness.Target);
using (UpdateSessionBarrier.SessionLease session =
harness.Manager.Barrier.AcquireSession())
{
await Assert.ThrowsAsync<LauncherUpdateException>(() =>
LauncherSelfUpdateBootstrap.HandleAsync(
["ordinary"],
harness.Manager,
harness.Target,
harness.LauncherPath));
}
SelfUpdatePlan rolledBack = await harness.Manager
.RollbackAwaitingConfirmationAsync(harness.Target);
using (UpdateSessionBarrier.SessionLease session =
harness.Manager.Barrier.AcquireSession())
{
await Assert.ThrowsAsync<LauncherUpdateException>(() =>
LauncherSelfUpdateBootstrap.HandleAsync(
["ordinary"],
harness.Manager,
harness.Target,
harness.LauncherPath));
}
await SetPlanStateAsync(harness.Manager.PendingPlanPath, "applying");
using (UpdateSessionBarrier.SessionLease session =
harness.Manager.Barrier.AcquireSession())
{
await Assert.ThrowsAsync<LauncherUpdateException>(() =>
LauncherSelfUpdateBootstrap.HandleAsync(
["ordinary"],
harness.Manager,
harness.Target,
harness.LauncherPath));
}
Assert.Equal(SelfUpdatePlanState.AwaitingConfirmation, awaiting.State);
Assert.Equal(SelfUpdatePlanState.RolledBack, rolledBack.State);
}
[Fact]
public async Task OrdinaryStartupRecoversApplyingAndFinalizesVerifiedRollback()
{
using var harness = new Harness(_root);
_ = await harness.StageAsync();
_ = await harness.Manager.ApplyPendingAsync(harness.Target);
await SetPlanStateAsync(harness.Manager.PendingPlanPath, "applying");
SelfUpdateStartupResult result = await LauncherSelfUpdateBootstrap.HandleAsync(
["ordinary"],
harness.Manager,
harness.Target,
harness.LauncherPath);
Assert.False(confirmation.ShouldExit);
Assert.Equal(publicArguments, confirmation.RemainingArguments);
Assert.False(File.Exists(harness.Manager.PendingPlanPath));
Assert.False(harness.Manager.IsConfirmed(applied.TransactionId));
Assert.False(result.ShouldExit);
Assert.Equal(["ordinary"], result.RemainingArguments);
Assert.Null(await harness.Manager.LoadPendingAsync());
Assert.Equal("old-launcher", await File.ReadAllTextAsync(harness.LauncherPath));
Assert.Equal("old-support", await File.ReadAllTextAsync(harness.SupportPath));
}
[Fact]
public async Task InternalPrefixSpoofsCannotCrossPlanStateOrExecutableTrust()
{
using var harness = new Harness(_root);
_ = await harness.StageAsync();
SelfUpdatePlan staged = Assert.IsType<SelfUpdatePlan>(
await harness.Manager.LoadPendingAsync());
await Assert.ThrowsAsync<LauncherUpdateException>(() =>
LauncherSelfUpdateBootstrap.HandleAsync(
[
LauncherSelfUpdateBootstrap.HelperArgument,
int.MaxValue.ToString(
System.Globalization.CultureInfo.InvariantCulture),
harness.Target,
staged.TransactionId,
],
harness.Manager,
harness.Target,
harness.LauncherPath));
await Assert.ThrowsAsync<LauncherUpdateException>(() =>
LauncherSelfUpdateBootstrap.HandleAsync(
[LauncherSelfUpdateBootstrap.ConfirmArgument, staged.TransactionId],
harness.Manager,
harness.Target,
harness.LauncherPath));
SelfUpdatePlan awaiting = await harness.Manager.ApplyPendingAsync(harness.Target);
await Assert.ThrowsAsync<LauncherUpdateException>(() =>
LauncherSelfUpdateBootstrap.HandleAsync(
[LauncherSelfUpdateBootstrap.ConfirmArgument, awaiting.TransactionId],
harness.Manager,
harness.Target,
Path.Combine(harness.Target, "spoof-launcher")));
await Assert.ThrowsAsync<LauncherUpdateException>(() =>
LauncherSelfUpdateBootstrap.HandleAsync(
[
LauncherSelfUpdateBootstrap.HelperArgument,
int.MaxValue.ToString(
System.Globalization.CultureInfo.InvariantCulture),
harness.Target,
awaiting.TransactionId,
],
harness.Manager,
harness.Target,
harness.LauncherPath));
SelfUpdatePlan rolledBack = await harness.Manager
.RollbackAwaitingConfirmationAsync(harness.Target);
foreach (string prefix in new[]
{
LauncherSelfUpdateBootstrap.HelperArgument,
LauncherSelfUpdateBootstrap.ConfirmArgument,
})
{
string[] arguments = prefix == LauncherSelfUpdateBootstrap.HelperArgument
? [prefix, int.MaxValue.ToString(), harness.Target, rolledBack.TransactionId]
: [prefix, rolledBack.TransactionId];
await Assert.ThrowsAsync<LauncherUpdateException>(() =>
LauncherSelfUpdateBootstrap.HandleAsync(
arguments,
harness.Manager,
harness.Target,
harness.LauncherPath));
}
SelfUpdateStartupResult deferred = await LauncherSelfUpdateBootstrap.HandleAsync(
["--acdream-self-update-deferred-v1"],
harness.Manager,
harness.Target,
harness.LauncherPath);
Assert.True(deferred.ShouldExit);
Assert.Equal(64, deferred.ExitCode);
await File.WriteAllTextAsync(harness.Manager.PendingPlanPath, "{ambiguous");
await Assert.ThrowsAsync<LauncherUpdateException>(() =>
LauncherSelfUpdateBootstrap.HandleAsync(
[LauncherSelfUpdateBootstrap.ConfirmArgument, rolledBack.TransactionId],
harness.Manager,
harness.Target,
harness.LauncherPath));
await Assert.ThrowsAsync<LauncherUpdateException>(() =>
LauncherSelfUpdateBootstrap.HandleAsync(
[
LauncherSelfUpdateBootstrap.HelperArgument,
int.MaxValue.ToString(),
harness.Target,
rolledBack.TransactionId,
],
harness.Manager,
harness.Target,
harness.LauncherPath));
}
private static async Task SetPlanStateAsync(string path, string state)
{
JsonObject plan = Assert.IsType<JsonObject>(JsonNode.Parse(
await File.ReadAllTextAsync(path)));
plan["state"] = state;
await File.WriteAllTextAsync(path, plan.ToJsonString());
}
private sealed class Harness : IDisposable