Skip to content

Commit c5a5707

Browse files
authored
Merge pull request #753 from fluxcd/libgit2-ssh-race-fixes
libgit2/managed: fix race conditions in ssh transport
2 parents dc1037d + 7f7490e commit c5a5707

File tree

1 file changed

+57
-32
lines changed

1 file changed

+57
-32
lines changed

pkg/git/libgit2/managed/ssh.go

+57-32
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)