Skip to content

Commit 283a094

Browse files
committed
fix: prevent data races in MemFs Rename and RemoveAll
Rename: If unRegisterWithParent or renameDescendants returns an error, the function returns while holding an exclusive write lock, but the deferred RUnlock runs instead of Unlock. RemoveAll: Unsafe modification of data map under the assumption that all entries belong to unregistered path. This implementaiton also raced with non-standard implicit MkdirAll behavior of Create.
1 parent 2f116ee commit 283a094

File tree

3 files changed

+143
-69
lines changed

3 files changed

+143
-69
lines changed

mem/file.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ func SetGID(f *FileData, gid int) {
113113
f.Unlock()
114114
}
115115

116+
func SetUIDGID(f *FileData, uid, gid int) {
117+
f.Lock()
118+
f.uid = uid
119+
f.gid = gid
120+
f.Unlock()
121+
}
122+
116123
func GetFileInfo(f *FileData) *FileInfo {
117124
return &FileInfo{f}
118125
}

memmap.go

Lines changed: 45 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ func (*MemMapFs) Name() string { return "MemMapFS" }
5555

5656
func (m *MemMapFs) Create(name string) (File, error) {
5757
name = normalizePath(name)
58-
m.mu.Lock()
5958
file := mem.CreateFile(name)
60-
m.getData()[name] = file
59+
60+
m.mu.Lock()
61+
defer m.mu.Unlock()
62+
6163
m.registerWithParent(file, 0)
62-
m.mu.Unlock()
64+
m.getData()[name] = file
6365
return mem.NewFileHandle(file), nil
6466
}
6567

@@ -162,18 +164,17 @@ func (m *MemMapFs) Mkdir(name string, perm os.FileMode) error {
162164
}
163165

164166
m.mu.Lock()
165-
// Dobule check that it doesn't exist.
166-
if _, ok := m.getData()[name]; ok {
167-
m.mu.Unlock()
167+
defer m.mu.Unlock()
168+
169+
if _, ok := m.getData()[name]; ok { // Recheck.
168170
return &os.PathError{Op: "mkdir", Path: name, Err: ErrFileExists}
169171
}
172+
170173
item := mem.CreateDir(name)
171174
mem.SetMode(item, os.ModeDir|perm)
172175
m.getData()[name] = item
173176
m.registerWithParent(item, perm)
174-
m.mu.Unlock()
175-
176-
return m.setFileMode(name, perm|os.ModeDir)
177+
return nil
177178
}
178179

179180
func (m *MemMapFs) MkdirAll(path string, perm os.FileMode) error {
@@ -253,8 +254,9 @@ func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, erro
253254
if err != nil {
254255
return nil, err
255256
}
257+
fileData := file.(*mem.File).Data()
256258
if flag == os.O_RDONLY {
257-
file = mem.NewReadOnlyFileHandle(file.(*mem.File).Data())
259+
file = mem.NewReadOnlyFileHandle(fileData)
258260
}
259261
if flag&os.O_APPEND > 0 {
260262
_, err = file.Seek(0, io.SeekEnd)
@@ -271,7 +273,7 @@ func (m *MemMapFs) OpenFile(name string, flag int, perm os.FileMode) (File, erro
271273
}
272274
}
273275
if chmod {
274-
return file, m.setFileMode(name, perm)
276+
mem.SetMode(fileData, perm)
275277
}
276278
return file, nil
277279
}
@@ -296,20 +298,19 @@ func (m *MemMapFs) Remove(name string) error {
296298

297299
func (m *MemMapFs) RemoveAll(path string) error {
298300
path = normalizePath(path)
299-
m.mu.Lock()
300-
m.unRegisterWithParent(path)
301-
m.mu.Unlock()
302301

303-
m.mu.RLock()
304-
defer m.mu.RUnlock()
302+
// The lock must be held throughout to ensure atomic consistency
303+
// between the parent registry and getData. This is a limitation
304+
// of the current data model.
305+
m.mu.Lock()
306+
defer m.mu.Unlock()
305307

308+
if err := m.unRegisterWithParent(path); err != nil {
309+
return nil
310+
}
306311
for p := range m.getData() {
307312
if p == path || strings.HasPrefix(p, path+FilePathSeparator) {
308-
m.mu.RUnlock()
309-
m.mu.Lock()
310313
delete(m.getData(), p)
311-
m.mu.Unlock()
312-
m.mu.RLock()
313314
}
314315
}
315316
return nil
@@ -324,32 +325,32 @@ func (m *MemMapFs) Rename(oldname, newname string) error {
324325
}
325326

326327
m.mu.RLock()
327-
defer m.mu.RUnlock()
328-
if _, ok := m.getData()[oldname]; ok {
329-
m.mu.RUnlock()
330-
m.mu.Lock()
331-
err := m.unRegisterWithParent(oldname)
332-
if err != nil {
333-
return err
334-
}
328+
_, ok := m.getData()[oldname]
329+
m.mu.RUnlock()
330+
if !ok {
331+
return &os.PathError{Op: "rename", Path: oldname, Err: ErrFileNotFound}
332+
}
335333

336-
fileData := m.getData()[oldname]
337-
mem.ChangeFileName(fileData, newname)
338-
m.getData()[newname] = fileData
334+
m.mu.Lock()
335+
defer m.mu.Unlock()
339336

340-
err = m.renameDescendants(oldname, newname)
341-
if err != nil {
342-
return err
343-
}
337+
err := m.unRegisterWithParent(oldname)
338+
if err != nil {
339+
return err
340+
}
344341

345-
delete(m.getData(), oldname)
342+
fileData := m.getData()[oldname]
343+
mem.ChangeFileName(fileData, newname)
344+
m.getData()[newname] = fileData
346345

347-
m.registerWithParent(fileData, 0)
348-
m.mu.Unlock()
349-
m.mu.RLock()
350-
} else {
351-
return &os.PathError{Op: "rename", Path: oldname, Err: ErrFileNotFound}
346+
err = m.renameDescendants(oldname, newname)
347+
if err != nil {
348+
return err
352349
}
350+
351+
delete(m.getData(), oldname)
352+
353+
m.registerWithParent(fileData, 0)
353354
return nil
354355
}
355356

@@ -391,6 +392,7 @@ func (m *MemMapFs) Stat(name string) (os.FileInfo, error) {
391392
}
392393

393394
func (m *MemMapFs) Chmod(name string, mode os.FileMode) error {
395+
name = normalizePath(name)
394396
mode &= chmodBits
395397

396398
m.mu.RLock()
@@ -400,25 +402,7 @@ func (m *MemMapFs) Chmod(name string, mode os.FileMode) error {
400402
return &os.PathError{Op: "chmod", Path: name, Err: ErrFileNotFound}
401403
}
402404
prevOtherBits := mem.GetFileInfo(f).Mode() & ^chmodBits
403-
404-
mode = prevOtherBits | mode
405-
return m.setFileMode(name, mode)
406-
}
407-
408-
func (m *MemMapFs) setFileMode(name string, mode os.FileMode) error {
409-
name = normalizePath(name)
410-
411-
m.mu.RLock()
412-
f, ok := m.getData()[name]
413-
m.mu.RUnlock()
414-
if !ok {
415-
return &os.PathError{Op: "chmod", Path: name, Err: ErrFileNotFound}
416-
}
417-
418-
m.mu.Lock()
419-
mem.SetMode(f, mode)
420-
m.mu.Unlock()
421-
405+
mem.SetMode(f, prevOtherBits|mode)
422406
return nil
423407
}
424408

@@ -431,10 +415,7 @@ func (m *MemMapFs) Chown(name string, uid, gid int) error {
431415
if !ok {
432416
return &os.PathError{Op: "chown", Path: name, Err: ErrFileNotFound}
433417
}
434-
435-
mem.SetUID(f, uid)
436-
mem.SetGID(f, gid)
437-
418+
mem.SetUIDGID(f, uid, gid)
438419
return nil
439420
}
440421

@@ -447,11 +428,7 @@ func (m *MemMapFs) Chtimes(name string, atime time.Time, mtime time.Time) error
447428
if !ok {
448429
return &os.PathError{Op: "chtimes", Path: name, Err: ErrFileNotFound}
449430
}
450-
451-
m.mu.Lock()
452431
mem.SetModTime(f, mtime)
453-
m.mu.Unlock()
454-
455432
return nil
456433
}
457434

memmap_test.go

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func TestPathErrors(t *testing.T) {
6767

6868
// fs.Create doesn't return an error
6969

70-
err = fs.Mkdir(path2, perm)
70+
err = fs.MkdirAll(path2, perm)
7171
if err != nil {
7272
t.Error(err)
7373
}
@@ -918,3 +918,93 @@ func TestMemMapFsRename(t *testing.T) {
918918
}
919919
}
920920
}
921+
922+
// TestMemMapFsRenameLockCorruption reproduces a bug where Rename returns while
923+
// holding a write lock but the deferred RUnlock runs, causing:
924+
//
925+
// RUnlock of unlocked RWMutex
926+
//
927+
// Test: go test . -race -count=10 -run=TestMemMapFsRenameLockCorruption
928+
func TestMemMapFsRenameLockCorruption(t *testing.T) {
929+
for range 10 {
930+
fs := NewMemMapFs()
931+
if err := fs.MkdirAll("/a/b", 0o755); err != nil {
932+
t.Fatal(err)
933+
}
934+
if _, err := fs.Create("/a/b/c"); err != nil {
935+
t.Fatal(err)
936+
}
937+
938+
var wg sync.WaitGroup
939+
wg.Add(2)
940+
941+
go func() {
942+
defer wg.Done()
943+
fs.RemoveAll("/a/b")
944+
}()
945+
946+
go func() {
947+
defer wg.Done()
948+
fs.Rename("/a/b/c", "/x")
949+
}()
950+
951+
done := make(chan struct{})
952+
go func() {
953+
wg.Wait()
954+
close(done)
955+
}()
956+
957+
select {
958+
case <-done:
959+
case <-time.After(5 * time.Second):
960+
t.Fatal("deadlock")
961+
}
962+
}
963+
}
964+
965+
// TestMemMapFsRemoveAllCreateRace reproduces a bug where a RemoveAll and Create
966+
// race leaves the filesystem in an inconsistent state where a file exists
967+
// without its parent.
968+
//
969+
// dir/file exists but /dir does not
970+
//
971+
// Test: go test . -race -count=100 -run=TestMemMapFsRemoveAllCreateRace
972+
func TestMemMapFsRemoveAllCreateRace(t *testing.T) {
973+
for range 10 {
974+
fs := NewMemMapFs()
975+
if err := fs.MkdirAll("/dir", 0o755); err != nil {
976+
t.Fatal(err)
977+
}
978+
979+
var wg sync.WaitGroup
980+
wg.Add(4)
981+
982+
go func() {
983+
defer wg.Done()
984+
fs.RemoveAll("/dir")
985+
}()
986+
go func() {
987+
defer wg.Done()
988+
fs.Create("/dir/file1")
989+
}()
990+
go func() {
991+
defer wg.Done()
992+
fs.Create("/dir/file2")
993+
}()
994+
go func() {
995+
defer wg.Done()
996+
fs.Create("/dir/file3")
997+
}()
998+
999+
wg.Wait()
1000+
1001+
_, dirErr := fs.Stat("/dir")
1002+
1003+
for _, name := range []string{"/dir/file1", "/dir/file2", "/dir/file3"} {
1004+
_, fileErr := fs.Stat(name)
1005+
if dirErr != nil && fileErr == nil {
1006+
t.Errorf("%s exists but /dir does not", name)
1007+
}
1008+
}
1009+
}
1010+
}

0 commit comments

Comments
 (0)