Created
March 18, 2015 18:15
-
-
Save hallyn/05df347ada107e072ac6 to your computer and use it in GitHub Desktop.
Exploit possible race in go-sqlite3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "database/sql" | |
| "fmt" | |
| "os" | |
| "time" | |
| "github.com/mattn/go-sqlite3" | |
| ) | |
| func IsDbLockedError(err error) bool { | |
| if err == nil { | |
| return false | |
| } | |
| if err == sqlite3.ErrLocked || err == sqlite3.ErrBusy { | |
| return true | |
| } | |
| if err.Error() == "database is locked" { | |
| return true | |
| } | |
| return false | |
| } | |
| func DbQuery(db *sql.DB, q string, args ...interface{}) (*sql.Rows, error) { | |
| slept := time.Millisecond * 0 | |
| for { | |
| result, err := db.Query(q, args...) | |
| if err == nil { | |
| return result, nil | |
| } | |
| if !IsDbLockedError(err) { | |
| fmt.Printf("DbQuery: query %q error %q\n", q, err) | |
| return nil, err | |
| } | |
| if slept == 30*time.Second { | |
| fmt.Printf("DB Locked for 30 seconds\n") | |
| return nil, err | |
| } | |
| time.Sleep(100 * time.Millisecond) | |
| slept = slept + 100*time.Millisecond | |
| } | |
| } | |
| func DbExec(db *sql.DB, q string, args ...interface{}) (sql.Result, error) { | |
| slept := time.Millisecond * 0 | |
| for { | |
| result, err := db.Exec(q, args...) | |
| if err == nil { | |
| return result, nil | |
| } | |
| if !IsDbLockedError(err) { | |
| fmt.Printf("DbExec: query %q error %q\n", q, err) | |
| return nil, err | |
| } | |
| if slept == 30*time.Second { | |
| fmt.Printf("DB Locked for 30 seconds\n") | |
| return nil, err | |
| } | |
| time.Sleep(100 * time.Millisecond) | |
| slept = slept + 100*time.Millisecond | |
| } | |
| } | |
| func main() { | |
| os.Remove("/tmp/x1") | |
| db, err := sql.Open("sqlite3", "/tmp/x1") | |
| if err != nil { | |
| fmt.Printf("Error creating database: %s\n", err) | |
| return | |
| } | |
| stmt := ` | |
| CREATE TABLE certificates ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, | |
| fingerprint int | |
| );` | |
| _, err = db.Exec(stmt); | |
| if err != nil { | |
| fmt.Printf("%q\n", err) | |
| return | |
| } | |
| fp := "Hi there, how are you?" | |
| _, err = db.Exec("insert into certificates (fingerprint) values (?);", fp) | |
| if err != nil { | |
| fmt.Printf("main: insert gave error %q\n", err) | |
| return | |
| } | |
| const nthreads int = 100 | |
| c := make(chan bool, nthreads) | |
| done := make(chan bool, nthreads) | |
| for i := 0; i < nthreads; i++ { | |
| me := i | |
| go func() { | |
| <-c | |
| fp2 := fmt.Sprintf("Hi there, how are you? I am %d", me) | |
| _, err = DbExec(db, "insert into certificates (fingerprint) values (?);", fp2) | |
| if err != nil { | |
| fmt.Printf("thread%d: insert gave error %q\n", me, err) | |
| done <- true | |
| return | |
| } | |
| rows, err := DbQuery(db, "select id from certificates where fingerprint=?", fp) | |
| if err != nil { | |
| fmt.Printf("thread%d: query gave error %qn", me, err) | |
| } | |
| defer rows.Close() | |
| id := -1 | |
| for rows.Next() { | |
| var idx int | |
| rows.Scan(&idx) | |
| id = idx | |
| } | |
| if id == -1 { | |
| fmt.Printf("thread%d: query gave no error, but results are empty\n", me) | |
| } | |
| done <- true | |
| return | |
| }() | |
| } | |
| for i := 0; i < nthreads; i++ { | |
| c <- true | |
| } | |
| for i := 0; i < nthreads; i++ { | |
| <-done | |
| } | |
| fmt.Printf("done\n") | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You should check that there is no error (Row.Err()) before checking that the results are empty: