Skip to content

Commit 5e673d4

Browse files
authored
fix: Prevent panic on concurrent sharing revocation (#4617)
A nil pointer dereference panic occurred at model/sharing/oauth.go:652 when multiple goroutines concurrently revoked the same sharing members. ``` 2025-11-27T10:35:49.710Z time="2025-11-27T10:35:49.683Z" level=error msg="[panic] runtime error: invalid memory address or nil pointer dereference: goroutine 8354870 [running]: runtime/debug.Stack() /home/jenkins/workspace/cozy-stack/gozy-bookworm-int-01-build/build/go/src/runtime/debug/stack.go:24 +0x5e github.com/cozy/cozy-stack/model/sharing.(*Sharing).Replicate.func1.1() /home/jenkins/workspace/cozy-stack/gozy-bookworm-int-01-build/build/cozy-stack/src/github.com/cozy/cozy-stack/model/sharing/replicator.go:74 +0x4f panic({0x161eda0?, 0x299b370?}) /home/jenkins/workspace/cozy-stack/gozy-bookworm-int-01-build/build/go/src/runtime/panic.go:770 +0x132 github.com/cozy/cozy-stack/model/sharing.RefreshToken(0xc0104578c8?, {0x1d7f680?, 0xc0118911d0?}, 0xc010a8a000?, 0xc010a980e8?, 0xc000277860, 0xc0000574c0, {0x0, 0x0, 0x0}) /home/jenkins/workspace/cozy-stack/gozy-bookworm-int-01-build/build/cozy-stack/src/github.com/cozy/cozy-stack/model/sharing/oauth.go:652 +0x115 github.com/cozy/cozy-stack/model/sharing.(*Sharing).NotifyMemberRevocation(0xc010a8a000, 0xc0104578c8, 0xc010a980e8, 0xc000277860) /home/jenkins/workspace/cozy-stack/gozy-bookworm-int-01-build/build/cozy-stack/src/github.com/cozy/cozy-stack/model/sharing/member.go:1071 +0x3d3 github.com/cozy/cozy-stack/model/sharing.(*Sharing).RevokeMember(0xc010a8a000, 0xc0104578c8, 0x2) /home/jenkins/workspace/cozy-stack/gozy-bookworm-int-01-build/build/cozy-stack/src/github.com/cozy/cozy-stack/model/sharing/member.go:965 +0xd0 github.com/cozy/cozy-stack/model/sharing.(*Sharing).Revoke(0xc010a8a000, 0xc0104578c8) /home/jenkins/workspace/cozy-stack/gozy-bookworm-int-01-build/build/cozy-stack/src/github.com/cozy/cozy-stack/model/sharing/sharing.go:457 +0xb1 github.com/cozy/cozy-stack/model/sharing.(*Sharing).ReplicateTo(0xc010a8a000, 0xc0104578c8, 0xc010a988c8, 0x0) /home/jenkins/workspace/cozy-stack/gozy-bookworm-int-01-build/build/cozy-stack/src/github.com/cozy/cozy-stack/model/sharing/replicator.go:193 +0x565 github.com/cozy/cozy-stack/model/sharing.(*Sharing).Replicate.func [TRUNCATED]" domain=cantoine2.mytoutatice.cloud ``` `Replicate()` spawns parallel goroutines for each member and each goroutine that encountered `errRevokeSharing` independently called `s.Revoke()`
2 parents 1453bee + 07cb6a2 commit 5e673d4

3 files changed

Lines changed: 18 additions & 9 deletions

File tree

model/sharing/member.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,12 @@ func (s *Sharing) DelegateRemoveReadOnlyFlag(inst *instance.Instance, index int)
958958
// RevokeMember revoke the access granted to a member and contact it
959959
func (s *Sharing) RevokeMember(inst *instance.Instance, index int) error {
960960
m := &s.Members[index]
961+
962+
// skip if member is already revoked
963+
if m.Status == MemberStatusRevoked {
964+
return nil
965+
}
966+
961967
c := &s.Credentials[index-1]
962968

963969
// No need to contact the revoked member if the sharing is not ready

model/sharing/oauth.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,10 @@ func RefreshToken(
649649
if err := creds.Refresh(inst, s, m); err != nil {
650650
return nil, err
651651
}
652+
// Safety check: credentials may have been cleared by a concurrent revocation
653+
if creds.AccessToken == nil {
654+
return nil, ErrNoOAuthClient
655+
}
652656
opts.Headers["Authorization"] = "Bearer " + creds.AccessToken.AccessToken
653657
if body != nil {
654658
opts.Body = bytes.NewReader(body)

model/sharing/replicator.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ type UpdateMsg struct {
5050
}
5151

5252
// Replicate starts a replicator on this sharing.
53-
func (s *Sharing) Replicate(inst *instance.Instance, errors int) error {
53+
func (s *Sharing) Replicate(inst *instance.Instance, errorsCount int) error {
5454
mu := config.Lock().ReadWrite(inst, "sharings/"+s.SID)
5555
if err := mu.Lock(); err != nil {
5656
return err
@@ -89,7 +89,13 @@ func (s *Sharing) Replicate(inst *instance.Instance, errors int) error {
8989
err = g.Wait()
9090
}
9191
if err != nil {
92-
s.retryWorker(inst, "share-replicate", errors)
92+
if errors.Is(err, errRevokeSharing) {
93+
if s.Owner {
94+
return s.Revoke(inst)
95+
}
96+
return s.RevokeRecipientBySelf(inst, false)
97+
}
98+
s.retryWorker(inst, "share-replicate", errorsCount)
9399
} else if pending {
94100
s.pushJob(inst, "share-replicate")
95101
}
@@ -188,13 +194,6 @@ func (s *Sharing) ReplicateTo(inst *instance.Instance, m *Member, initial bool)
188194

189195
feed, err := s.callChangesFeed(inst, lastSeq)
190196
if err != nil {
191-
if errors.Is(err, errRevokeSharing) {
192-
if s.Owner {
193-
return false, s.Revoke(inst)
194-
} else {
195-
return false, s.RevokeRecipientBySelf(inst, false)
196-
}
197-
}
198197
return false, err
199198
}
200199
if feed.Seq == lastSeq {

0 commit comments

Comments
 (0)