Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUGFIX: Scripts are killed too late when prestiging #1683

Conversation

catloversg
Copy link
Contributor

A player reports a bug with stock, ns.atExit and soft reset. The gist is that when they sell the stock in ns.atExit, the profit is added to the player's money after the soft reset.

MRE:

/** @param {NS} ns */
export async function main(ns) {
  ns.stock.buyStock("JGN", 100);
  ns.atExit(() => ns.stock.sellStock("JGN", 100));
  setTimeout(() => ns.singularity.softReset(), 10)
  while (true) await ns.asleep(100);
}

After the soft reset, the player has "initital money" + profit instead of just "initital money".

The bug happens because prestigeWorkerScripts() is called after Player.prestigeAugmentation() and before initStockMarket. The flow:

  • Player.prestigeAugmentation(): Reset the player's money.
  • prestigeWorkerScripts(): call ns.atExit.
  • ns.atExit sells stocks and adds the profit to the player's money.
  • initStockMarket: reset the stock market.

The fix is fairly simple: We only need to call prestigeWorkerScripts() before Player.prestigeAugmentation(). However, the problem is more complicated than that. Because prestigeWorkerScripts() will run ns.atExit, it allows the player to run arbitrary code when the game logic is performing the prestige and be in an incomplete transition state. This can create many subtle bugs/weird behaviors/exploits. This is an exploit that I come up with:

MRE:

/** @param {NS} ns */
export async function main(ns) {
  Player.money = 76e12;
  Player.receiveInvite("Illuminati");
  Factions.Illuminati.alreadyInvited = true;
  Factions.Illuminati.playerReputation = 1e12;
  Player.augmentations = [];
  Player.queuedAugmentations = [];

  ns.singularity.joinFaction("Illuminati");
  ns.singularity.purchaseAugmentation("Illuminati", "Synfibril Muscle");
  ns.atExit(() => {
    ns.singularity.purchaseAugmentation("Illuminati", "QLink")
    ns.singularity.installAugmentations();
  });
  ns.singularity.installAugmentations();
}

In this PoC:

  • Set up test data (you need to expose Player and Factions; you can use the dev tool as an alternative way):
    • Receive the invitation of Illuminati.
    • Have tons of reputation with Illuminati.
    • Have 76e12 cash: It's not enough to buy Synfibril Muscle and QLink in a single run, but it's enough to buy both augmentations if we can find a way to "reset" the cost requirement halfway.
  • Join Illuminati.
  • Buy Synfibril Muscle.
  • Set up ns.atExit.
  • Call ns.singularity.installAugmentations().

The cost of QLink will be increased after we buy Synfibril Muscle, but we can "reset" that cost requirement with this exploit.

To prevent this kind of problem, this PR does these things:

  • Call prestigeWorkerScripts as early as possible in prestigeAugmentation and prestigeSourceFile.
  • Check all callers of prestigeAugmentation and prestigeSourceFile. If those callers modify the game state (installing augmentations, setting BitNode data, etc.), we call prestigeWorkerScripts before those code.

@d0sboots
Copy link
Collaborator

d0sboots commented Oct 8, 2024

I think this is a good change. However, I wonder if there are not still holes such as doing ns.run or ns.spawn inside atExit, to further get around the prestige.

In the end, some level of that might be impossible to patch completely, and that might be OK. (Certainly there are plenty of other exploits that we acknowledge.)

@d0sboots d0sboots merged commit b829534 into bitburner-official:dev Oct 8, 2024
5 checks passed
@catloversg catloversg deleted the pull-request/bugfix/scripts-are-killed-too-late-when-prestiging branch October 8, 2024 07:57
@catloversg
Copy link
Contributor Author

I'll take a look at them (run, exec, spawn, etc.). If I find any problems, I'll open an issue/PR.

antoinedube pushed a commit to antoinedube/bitburner-source that referenced this pull request Oct 16, 2024
…al#1683)

* Scripts are killed too late when prestiging
* Kill all scripts earlier
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants