The Migration That Unblocked Everyone — Python Bug Hunt

Modelled on Facebook's block-list bug (disclosed July 2018): for about a week in late May and early June 2018, a bug temporarily unblocked people that more…

  • Language: Python
  • Layer: Database
  • Difficulty: Medium
  • Concepts: Security, State
  • Modelled on: Facebook · 2018
  • Visible tests: an update sets the flag it carries; a block survives an update that does not mention it
  • Reward: 50 XP for a complete fix

Briefing

Modelled on Facebook's block-list bug (disclosed July 2018): for about a week in late May and early June 2018, a bug temporarily unblocked people that more than 800,000 users had blocked — a blocked person could, for instance, contact them on Messenger again. Facebook fixed the bug, restored the blocks and notified the affected users.

Facebook did not publish the code involved; this project is a reconstruction of one way such a bug happens. migrate.py applies a batch of relationship-setting updates, normalising every flag to a boolean — so a flag the update never mentioned is rewritten to False.

Fix apply_updates so an update only changes the fields it actually carries.

Bug report

BUG-BLOCKLIST · Priority: Critical (privacy) · Reported by: trust & safety

apply_updates(rows, updates) — each update is {"a", "b", ...some of "blocked" / "following" / "muted"}:

  • the row for (a, b) is found with relations.find; an update with no matching row is skipped
  • ONLY the flags present in the update are written (as booleans); every flag the update does not mention keeps its current value
  • an update may explicitly set "blocked": False (a real unblock)
  • returns the number of rows that matched an update

Observed: a bulk update that only touched "following" cleared "blocked" on every row it matched.

Logs

[migrate] batch rel-settings-0529 applied to 812000 rows
[support] ticket: "someone I blocked just messaged me"

The code as shipped

src/social/migrate.py (editable)

relations = bug_require("./relations.py")

FIELDS = ("blocked", "following", "muted")


def apply_updates(rows, updates):
    matched = 0
    for u in updates:
        row = relations.find(rows, u["a"], u["b"])
        if row is None:
            continue
        for field in FIELDS:
            row[field] = bool(u.get(field, False))
        matched += 1
    return matched

Read-only context: src/social/relations.py.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More Python bug hunts.