-
Notifications
You must be signed in to change notification settings - Fork 13
Description
There is such a problem as the race condition attack.
This problem is famous when using the ORM pattern as Active Record and Data Mapper.
We get a record for changes and change it outside the transaction, which means that we can execute precisely the same query in parallel.
Lines 592 to 595 in 3729bf9
| const entity = await this.resolveEntities(ctx, params, { | |
| transform: false, | |
| throwIfNotExist: true | |
| }); |
Line 618 in 3729bf9
| result = await adapter.updateById(id, params, { raw: rawUpdate }); |
Consider a simple attack in transferring $5 from Alice to Bob:
Get balance Alice -> $5 -> transfer to Bob -$5 -> get balance Bob -> $0 -> received from Alice +$5
And now, if two queries were running at the same time:
Query 1: Get balance Alice -> $5 (race condition!) -> transfer to Bob -$5 -> get balance Bob -> $0 -> received from Alice +$5
Query 2: Get balance Alice -> $5 (race condition!) -> transfer to Bob -$5 -> get balance Bob -> $5 -> recieved from Alice +$5
Bob has a balance of $10, and Alice has $5.
It is elementary to check this behaviour by calling the Promise.all() method, we generate ten requests and, depending on the network, and the processing speed of the database, from 2 to 10 requests will pass simultaneously.
In SQL, the first statement must be with SELECT FOR UPDATE to block against concurrent updates, or both wrapped in a SERIALIZABLE transaction isolation level.
How can we protect ourselves now?
We must wrap the updateEntity method in a transaction with isolation level SERIALIZABLE.
Or use moleculer-channels to update items in strong FIFO in Kafka.