Skip to content

Commit 7f7490e

Browse files
committed
libgit2/managed: fix race issues in ssh transport
Race conditions in ssh smart subtransport caused some goroutines to panic, resulting in crashing the whole controller, mostly evident in image-automation-controller CI runs. Panic recovery in the main thread do not handle goroutine panics. So, the existing panic recovery code in libgit2 Checkout() methods weren't able to handle it. This change groups the fields in ssh smart subtransport that may be accessed by multiple goroutines into a new struct with a mutex. Also adds panic recovery in the created goroutine to handle any other possible panics. Signed-off-by: Sunny <[email protected]>
1 parent dc1037d commit 7f7490e

File tree

1 file changed

+57
-32
lines changed

1 file changed

+57
-32
lines changed

pkg/git/libgit2/managed/ssh.go

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@ package managed
4646
import (
4747
"context"
4848
"crypto/sha256"
49+
"errors"
4950
"fmt"
5051
"io"
5152
"net"
5253
"net/url"
5354
"runtime"
5455
"strings"
56+
"sync"
5557
"time"
5658

5759
"golang.org/x/crypto/ssh"
@@ -83,16 +85,22 @@ func sshSmartSubtransportFactory(remote *git2go.Remote, transport *git2go.Transp
8385
type sshSmartSubtransport struct {
8486
transport *git2go.Transport
8587

86-
lastAction git2go.SmartServiceAction
88+
lastAction git2go.SmartServiceAction
89+
stdin io.WriteCloser
90+
stdout io.Reader
91+
addr string
92+
ctx context.Context
93+
94+
con connection
95+
}
96+
97+
type connection struct {
8798
conn net.Conn
8899
client *ssh.Client
89100
session *ssh.Session
90-
stdin io.WriteCloser
91-
stdout io.Reader
92101
currentStream *sshSmartSubtransportStream
93-
addr string
94102
connected bool
95-
ctx context.Context
103+
m sync.Mutex
96104
}
97105

98106
func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.SmartServiceAction) (git2go.SmartSubtransportStream, error) {
@@ -128,17 +136,17 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
128136
var cmd string
129137
switch action {
130138
case git2go.SmartServiceActionUploadpackLs, git2go.SmartServiceActionUploadpack:
131-
if t.currentStream != nil {
139+
if t.con.currentStream != nil {
132140
if t.lastAction == git2go.SmartServiceActionUploadpackLs {
133-
return t.currentStream, nil
141+
return t.con.currentStream, nil
134142
}
135143
}
136144
cmd = fmt.Sprintf("git-upload-pack '%s'", uPath)
137145

138146
case git2go.SmartServiceActionReceivepackLs, git2go.SmartServiceActionReceivepack:
139-
if t.currentStream != nil {
147+
if t.con.currentStream != nil {
140148
if t.lastAction == git2go.SmartServiceActionReceivepackLs {
141-
return t.currentStream, nil
149+
return t.con.currentStream, nil
142150
}
143151
}
144152
cmd = fmt.Sprintf("git-receive-pack '%s'", uPath)
@@ -147,7 +155,7 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
147155
return nil, fmt.Errorf("unexpected action: %v", action)
148156
}
149157

150-
if t.connected {
158+
if t.con.connected {
151159
// Disregard errors from previous stream, futher details inside Close().
152160
_ = t.Close()
153161
}
@@ -185,21 +193,23 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
185193
if err != nil {
186194
return nil, err
187195
}
188-
t.connected = true
196+
t.con.m.Lock()
197+
t.con.connected = true
198+
t.con.m.Unlock()
189199

190200
traceLog.Info("[ssh]: creating new ssh session")
191-
if t.session, err = t.client.NewSession(); err != nil {
201+
if t.con.session, err = t.con.client.NewSession(); err != nil {
192202
return nil, err
193203
}
194204

195-
if t.stdin, err = t.session.StdinPipe(); err != nil {
205+
if t.stdin, err = t.con.session.StdinPipe(); err != nil {
196206
return nil, err
197207
}
198208

199209
var w *io.PipeWriter
200210
var reader io.Reader
201211
t.stdout, w = io.Pipe()
202-
if reader, err = t.session.StdoutPipe(); err != nil {
212+
if reader, err = t.con.session.StdoutPipe(); err != nil {
203213
return nil, err
204214
}
205215

@@ -208,7 +218,15 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
208218
//
209219
// xref: https://github.com/golang/crypto/blob/eb4f295cb31f7fb5d52810411604a2638c9b19a2/ssh/session.go#L553-L558
210220
go func() error {
211-
defer w.Close()
221+
defer func() {
222+
w.Close()
223+
224+
// In case this goroutine panics, handle recovery.
225+
if r := recover(); r != nil {
226+
traceLog.Error(errors.New(r.(string)),
227+
"[ssh]: recovered from libgit2 ssh smart subtransport panic", "address", t.addr)
228+
}
229+
}()
212230

213231
var cancel context.CancelFunc
214232
ctx := t.ctx
@@ -226,9 +244,12 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
226244
return nil
227245

228246
default:
229-
if !t.connected {
247+
t.con.m.Lock()
248+
if !t.con.connected {
249+
t.con.m.Unlock()
230250
return nil
231251
}
252+
t.con.m.Unlock()
232253

233254
_, err := io.Copy(w, reader)
234255
if err != nil {
@@ -240,16 +261,16 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
240261
}()
241262

242263
traceLog.Info("[ssh]: run on remote", "cmd", cmd)
243-
if err := t.session.Start(cmd); err != nil {
264+
if err := t.con.session.Start(cmd); err != nil {
244265
return nil, err
245266
}
246267

247268
t.lastAction = action
248-
t.currentStream = &sshSmartSubtransportStream{
269+
t.con.currentStream = &sshSmartSubtransportStream{
249270
owner: t,
250271
}
251272

252-
return t.currentStream, nil
273+
return t.con.currentStream, nil
253274
}
254275

255276
func (t *sshSmartSubtransport) createConn(addr string, sshConfig *ssh.ClientConfig) error {
@@ -265,8 +286,8 @@ func (t *sshSmartSubtransport) createConn(addr string, sshConfig *ssh.ClientConf
265286
return err
266287
}
267288

268-
t.conn = conn
269-
t.client = ssh.NewClient(c, chans, reqs)
289+
t.con.conn = conn
290+
t.con.client = ssh.NewClient(c, chans, reqs)
270291

271292
return nil
272293
}
@@ -282,31 +303,35 @@ func (t *sshSmartSubtransport) createConn(addr string, sshConfig *ssh.ClientConf
282303
// SmartSubTransport (i.e. unreleased resources, staled connections).
283304
func (t *sshSmartSubtransport) Close() error {
284305
traceLog.Info("[ssh]: sshSmartSubtransport.Close()", "server", t.addr)
285-
t.currentStream = nil
286-
if t.client != nil && t.stdin != nil {
306+
t.con.m.Lock()
307+
defer t.con.m.Unlock()
308+
t.con.currentStream = nil
309+
if t.con.client != nil && t.stdin != nil {
287310
_ = t.stdin.Close()
288311
}
289-
t.client = nil
312+
t.con.client = nil
290313

291-
if t.session != nil {
314+
if t.con.session != nil {
292315
traceLog.Info("[ssh]: session.Close()", "server", t.addr)
293-
_ = t.session.Close()
316+
_ = t.con.session.Close()
294317
}
295-
t.session = nil
318+
t.con.session = nil
296319

297320
return nil
298321
}
299322

300323
func (t *sshSmartSubtransport) Free() {
301324
traceLog.Info("[ssh]: sshSmartSubtransport.Free()")
302-
if t.client != nil {
303-
_ = t.client.Close()
325+
if t.con.client != nil {
326+
_ = t.con.client.Close()
304327
}
305328

306-
if t.conn != nil {
307-
_ = t.conn.Close()
329+
if t.con.conn != nil {
330+
_ = t.con.conn.Close()
308331
}
309-
t.connected = false
332+
t.con.m.Lock()
333+
t.con.connected = false
334+
t.con.m.Unlock()
310335
}
311336

312337
type sshSmartSubtransportStream struct {

0 commit comments

Comments
 (0)