question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Optimistic Concurrency Control pattern from documentation sometimes lets conflicting writes go through?

See original GitHub issue

Bug description

Context: Iā€™m building a one-time-password login scheme that generates a 6 digit password and emails it to the user. To prevent brute force attacks, Iā€™m locking an account for 24 hours after 5 failed attempts. So itā€™s really important that attackers donā€™t get more attempts to crack a password if they send a lot of requests concurrently.

Iā€™m using the Optimistic Concurrency Control pattern shared in the documentation. But seeing that when I write a simple test that makes a bunch of attempts to login concurrently, some conflicting updates are allowed through.

How to reproduce

Repo with code to reproduce the problem: https://github.com/calebmer/prisma-optimistic-concurrency-control-pattern-conflict-repro

Run node repro.js a couple times. I have an assert with the expected output and it fails for me maybe 2 out of 3 times.

I tried to make a minimal reproducing case, but didnā€™t remove my domain model. (So the repro is still in the context of one-time-password login.)

Expected behavior

The repro code attempts to login 16 times with incorrect passwords. We should only expose to an attacker that the password was incorrect 5 times and then tell the attacker the account is locked 11 times. (An assert encodes this expectation.)

I donā€™t have the code for a ā€œsuccessfulā€ login, but it also uses the Optimistic Concurrency Control pattern and is subject to the same conflicting write issue (which Iā€™ve observed in local tests), effectively giving attackers more attempts to crack the password.

Prisma information

(Prisma schema, version, and queries included in the repro.)

Environment & setup

  • OS: MacOS
  • Database: PostgreSQL
  • Node.js version: v15.0.1
  • Prisma version:
prisma               : 2.21.2
@prisma/client       : 2.21.2
Current platform     : darwin
Query Engine         : query-engine e421996c87d5f3c8f7eeadd502d4ad402c89464d (at node_modules/@prisma/engines/query-engine-darwin)
Migration Engine     : migration-engine-cli e421996c87d5f3c8f7eeadd502d4ad402c89464d (at node_modules/@prisma/engines/migration-engine-darwin)
Introspection Engine : introspection-core e421996c87d5f3c8f7eeadd502d4ad402c89464d (at node_modules/@prisma/engines/introspection-engine-darwin)
Format Binary        : prisma-fmt e421996c87d5f3c8f7eeadd502d4ad402c89464d (at node_modules/@prisma/engines/prisma-fmt-darwin)
Default Engines Hash : e421996c87d5f3c8f7eeadd502d4ad402c89464d
Studio               : 0.371.0

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:1
  • Comments:9 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
calebmercommented, May 7, 2021

For more details on my actual code, the ā€œsuccessfully verified password branchā€ looks something like this:

const updateAccountResult = await prisma.account.updateMany({
  where: {
    id: account.id,
    // Include `failedOneTimePasswordLoginAttempts` in our `where` clause
    // because if it changed concurrently then the account may actually
    // be locked so weā€™ll want to skip the update.
    failedOneTimePasswordLoginAttempts:
      account.failedOneTimePasswordLoginAttempts,
  },
  data: {
    // Unset the one time password after it has been successfully used to
    // guarantee it wonā€™t be used again.
    oneTimePasswordLoginHash: null,
    // Reset failed login attempt fields after successful login.
    failedOneTimePasswordLoginAttempts: 0,
  },
});

So it uses the same OOC ā€œcheckā€ in the where clause but doesnā€™t need to atomically update any values. Iā€™ve also observed this branch incorrectly fire when testing locally. (Naturally this is the dangerous branch to incorrectly fire since it logs the attacker in.)

0reactions
matthewmuellercommented, Jun 15, 2021

Closing. Happy to re-open this discussion if needed though.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Database Concurrency Conflicts in the Real World
When using optimistic concurrency control the user doesn't lock the data to prevent concurrency issues but detects and resolves them when writing to...
Read more >
Handling Concurrency Conflicts - EF6 - Microsoft Learn
Optimistic concurrency involves optimistically attempting to save your entity to the database in the hope that the data there has notĀ ...
Read more >
PostgreSQL anti-patterns: read-modify-write cycles - EDB
Optimistic concurrency control, otherwise known as optimistic locking. Avoiding the read-modify-write cycle. The best solution is often to justĀ ...
Read more >
Optimistic Concurrency Control - Apache Software Foundation
Optimistic Concurrency using atomic renames with Conflict Resolution ... This RFC proposes to introduce the ability for concurrent writers to Hudi,Ā ...
Read more >
Transactions - Prisma
Idempotent operations; Optimistic concurrency control; Interactive transactions ... Note: For the purposes of this guide, writing to a database encompassesĀ ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found