Race condition
Need
Prevention of race conditions in the system
Context
- Usage of Go 1.16 for developing scalable and efficient applications
- Usage of Gin framework for building web applications in Go
Description
Non compliant code
package main
import (
"sync"
"time"
"github.com/gin-gonic/gin"
)
var counter int
func main() {
r := gin.Default()
var mu sync.Mutex
r.GET("/increment", func(c *gin.Context) {
go func() {
mu.Lock()
counter++
mu.Unlock()
}()
c.JSON(200, gin.H{
"message": "Incremented!",
})
})
r.GET("/get_counter", func(c *gin.Context) {
c.JSON(200, gin.H{
"counter": counter,
})
})
r.Run()
}
In the code above, we have a simple server with two routes: /increment
and /get_counter
.
The /increment
route starts a new goroutine to increment a global variable counter
. It uses a mutex mu
to lock the critical section where the increment operation is performed. However, the mutex lock and unlock are both within the goroutine.
This introduces a race condition. If the /increment
route is hit multiple times in quick succession, multiple goroutines may be spawned to perform the increment operation. Since the mutex lock and unlock are both within the goroutine, the lock may not be effective in preventing other goroutines from entering the critical section.
This can lead to the counter
variable not being incremented correctly. For example, if two goroutines enter the critical section at the same time, they might both read the counter
as 0, increment it to 1, and write back 1. This results in the counter
being 1 instead of 2, even though the /increment
route was hit twice.
The /get_counter
route simply returns the current value of the counter
. This value may not be accurate due to the race condition described above.
Steps
- Identify the critical section of code where the race condition is occurring.
- Use a synchronization mechanism to protect the critical section from concurrent access.
- In this case, the vulnerable code is using a global counter variable, so we can use a mutex to synchronize access to it.
- Create a mutex object to control access to the counter variable.
- Before incrementing the counter, lock the mutex to ensure exclusive access.
- After incrementing the counter, unlock the mutex to allow other goroutines to access it.
- Repeat steps 5 and 6 for any other code that accesses or modifies the counter variable.
- Test the application to ensure that the race condition has been resolved.
Compliant code
package main
import (
"sync"
"github.com/gin-gonic/gin"
)
var counter int
var mu sync.Mutex
func main() {
r := gin.Default()
r.GET("/increment", func(c *gin.Context) {
mu.Lock()
defer mu.Unlock()
counter++
c.JSON(200, gin.H{
"message": "Incremented!",
})
})
r.GET("/get_counter", func(c *gin.Context) {
mu.Lock()
defer mu.Unlock()
c.JSON(200, gin.H{
"counter": counter,
})
})
r.Run()
}
The original code had a race condition vulnerability due to the concurrent access of the global variable counter
. This could lead to unexpected behavior if the /increment
and /get_counter
endpoints were accessed simultaneously from different goroutines.
The fixed code uses a mutex, mu
, to synchronize access to the counter
variable. This ensures that only one goroutine can access the counter
at a time, preventing the race condition.
In the /increment
endpoint, the mutex is locked before the counter
is incremented and unlocked immediately after. The defer
keyword is used to ensure that the mutex is unlocked even if an error occurs. This prevents a potential deadlock situation where the mutex remains locked indefinitely.
Similarly, in the /get_counter
endpoint, the mutex is locked before the counter
is accessed and unlocked immediately after. This ensures that the value of counter
cannot be changed by another goroutine while it is being read.
By using a mutex to control access to the counter
variable, the race condition vulnerability is resolved. The application should be tested thoroughly to ensure that the fix is effective and does not introduce new issues.