Skip to content

TOCTOU Race for file ref updates #2600

@willstott101

Description

Current behavior 😯

When updating a file ref, gitoxide reads the current value to check against the Edit, for example PreviousValue::MustExistAndMatch() before locking that ref for updates. This means multiple concurrent edits can overwrite each other silently, and the returned value that's meant to show what got overwritten will be incorrect.

This is particularly important when multiple concurrent pushes are using --force-with-lease and the ref updates are being handled server-side by gitoxide. Two pushes could read the same ref, then both try to lock, and then both update. Only one of them should have been successful, and someone's push looked successful but was then silently thrown away 😱

I have an Opus-written fix for this, and am happy to open a PR. Basically it comes down to this block beiing moved into a function that can then be called whilst holding the lock:

Expected behavior 🤔

I would expect lock_ref_and_apply_change to be "safe" in concurrent environments, in the sense that it would compare against the same value it was overwriting.

Git behavior

No response

Steps to reproduce 🕹

It's kind of a fiddly one tbh. There's a test in mizzle that reproduces it quite consistently on the slow github actions runners: https://github.com/willstott101/mizzle/actions/runs/25799779550/job/75786315209

But the broad steps are:

  • open a repo multiple times in separate threads
  • try to update the refs with constraints like MustExistAndMatch where they all expect the same initial value
  • the bug is surfaced if more than one thread returns as successfully updating the ref

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions