diff -Nru syncthing-1.12.1~ds1/debian/changelog syncthing-1.12.1~ds1/debian/changelog --- syncthing-1.12.1~ds1/debian/changelog 2021-02-04 11:26:39.000000000 -0500 +++ syncthing-1.12.1~ds1/debian/changelog 2021-07-15 11:47:47.000000000 -0400 @@ -1,8 +1,21 @@ +syncthing (1.12.1~ds1-3) unstable; urgency=medium + + [ Alexandre Viau] + * Refresh patches. + + [ Simon Frei ] + * Patch CVE-2021-21404 (Closes: #986593). + * Fix possible incorrect remote sync (Closes: #983667). + * Fix dead-lock in index-sender (Closes: #983668). + * Fix mismatching index-ids (Closes: #983669). + * Fix connection taking too long to cose (Closes: 983670). + + -- Alexandre Viau Thu, 15 Jul 2021 11:47:47 -0400 + syncthing (1.12.1~ds1-2) unstable; urgency=medium * golang-gogoprotobuf-dev -> golang-github-gogo-protobuf-dev. - -- Alexandre Viau Thu, 04 Feb 2021 11:26:39 -0500 syncthing (1.12.1~ds1-1) unstable; urgency=medium diff -Nru syncthing-1.12.1~ds1/debian/patches/0001-lib-db-Prevent-IndexID-creation-race-7211.patch syncthing-1.12.1~ds1/debian/patches/0001-lib-db-Prevent-IndexID-creation-race-7211.patch --- syncthing-1.12.1~ds1/debian/patches/0001-lib-db-Prevent-IndexID-creation-race-7211.patch 1969-12-31 19:00:00.000000000 -0500 +++ syncthing-1.12.1~ds1/debian/patches/0001-lib-db-Prevent-IndexID-creation-race-7211.patch 2021-07-15 11:47:47.000000000 -0400 @@ -0,0 +1,127 @@ +From 5ee8b8a6aa4b5366a721f0531ef4df45780a3801 Mon Sep 17 00:00:00 2001 +From: Simon Frei +Date: Mon, 21 Dec 2020 11:32:59 +0100 +Subject: [PATCH 1/4] lib/db: Prevent IndexID creation race (#7211) + +--- + lib/db/lowlevel.go | 2 ++ + lib/db/set.go | 33 ++++++++++++++++++++++----------- + lib/db/set_test.go | 26 ++++++++++++++++++++++++++ + 3 files changed, 50 insertions(+), 11 deletions(-) + +--- a/lib/db/lowlevel.go ++++ b/lib/db/lowlevel.go +@@ -65,6 +65,7 @@ + gcKeyCount int + indirectGCInterval time.Duration + recheckInterval time.Duration ++ oneFileSetCreated chan struct{} + } + + func NewLowlevel(backend backend.Backend, opts ...Option) *Lowlevel { +@@ -81,6 +82,7 @@ + gcMut: sync.NewRWMutex(), + indirectGCInterval: indirectGCDefaultInterval, + recheckInterval: recheckDefaultInterval, ++ oneFileSetCreated: make(chan struct{}), + } + for _, opt := range opts { + opt(db) +--- a/lib/db/set.go ++++ b/lib/db/set.go +@@ -39,13 +39,27 @@ + type Iterator func(f protocol.FileIntf) bool + + func NewFileSet(folder string, fs fs.Filesystem, db *Lowlevel) *FileSet { +- return &FileSet{ ++ select { ++ case <-db.oneFileSetCreated: ++ default: ++ close(db.oneFileSetCreated) ++ } ++ s := &FileSet{ + folder: folder, + fs: fs, + db: db, + meta: db.loadMetadataTracker(folder), + updateMutex: sync.NewMutex(), + } ++ if id := s.IndexID(protocol.LocalDeviceID); id == 0 { ++ // No index ID set yet. We create one now. ++ id = protocol.NewIndexID() ++ err := s.db.setIndexID(protocol.LocalDeviceID[:], []byte(s.folder), id) ++ if err != nil && !backend.IsClosed(err) { ++ fatalError(err, fmt.Sprintf("%s Creating new IndexID", s.folder), s.db) ++ } ++ } ++ return s + } + + func (s *FileSet) Drop(device protocol.DeviceID) { +@@ -357,16 +371,6 @@ + } else if err != nil { + fatalError(err, opStr, s.db) + } +- if id == 0 && device == protocol.LocalDeviceID { +- // No index ID set yet. We create one now. +- id = protocol.NewIndexID() +- err := s.db.setIndexID(device[:], []byte(s.folder), id) +- if backend.IsClosed(err) { +- return 0 +- } else if err != nil { +- fatalError(err, opStr, s.db) +- } +- } + return id + } + +@@ -432,7 +436,14 @@ + + // DropDeltaIndexIDs removes all delta index IDs from the database. + // This will cause a full index transmission on the next connection. ++// Must be called before using FileSets, i.e. before NewFileSet is called for ++// the first time. + func DropDeltaIndexIDs(db *Lowlevel) { ++ select { ++ case <-db.oneFileSetCreated: ++ panic("DropDeltaIndexIDs must not be called after NewFileSet for the same Lowlevel") ++ default: ++ } + opStr := "DropDeltaIndexIDs" + l.Debugf(opStr) + dbi, err := db.NewPrefixIterator([]byte{KeyTypeIndexID}) +--- a/lib/db/set_test.go ++++ b/lib/db/set_test.go +@@ -1757,6 +1757,32 @@ + } + } + ++func TestConcurrentIndexID(t *testing.T) { ++ done := make(chan struct{}) ++ var ids [2]protocol.IndexID ++ setID := func(s *db.FileSet, i int) { ++ ids[i] = s.IndexID(protocol.LocalDeviceID) ++ done <- struct{}{} ++ } ++ ++ max := 100 ++ if testing.Short() { ++ max = 10 ++ } ++ for i := 0; i < max; i++ { ++ ldb := db.NewLowlevel(backend.OpenMemory()) ++ s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeFake, ""), ldb) ++ go setID(s, 0) ++ go setID(s, 1) ++ <-done ++ <-done ++ ldb.Close() ++ if ids[0] != ids[1] { ++ t.Fatalf("IDs differ after %v rounds", i) ++ } ++ } ++} ++ + func replace(fs *db.FileSet, device protocol.DeviceID, files []protocol.FileInfo) { + fs.Drop(device) + fs.Update(device, files) diff -Nru syncthing-1.12.1~ds1/debian/patches/0002-lib-Close-underlying-conn-in-protocol-fixes-7165-721.patch syncthing-1.12.1~ds1/debian/patches/0002-lib-Close-underlying-conn-in-protocol-fixes-7165-721.patch --- syncthing-1.12.1~ds1/debian/patches/0002-lib-Close-underlying-conn-in-protocol-fixes-7165-721.patch 1969-12-31 19:00:00.000000000 -0500 +++ syncthing-1.12.1~ds1/debian/patches/0002-lib-Close-underlying-conn-in-protocol-fixes-7165-721.patch 2021-07-15 11:47:47.000000000 -0400 @@ -0,0 +1,570 @@ +From dfc03a6a37f9e13a151754fb5b3ea4a9aeaae94f Mon Sep 17 00:00:00 2001 +From: Simon Frei +Date: Mon, 21 Dec 2020 11:40:51 +0100 +Subject: [PATCH 2/4] lib: Close underlying conn in protocol (fixes #7165) + (#7212) + +--- + lib/api/mocked_model_test.go | 5 ++-- + lib/connections/service.go | 7 ++--- + lib/connections/structs.go | 33 +++------------------ + lib/model/fakeconns_test.go | 52 ++-------------------------------- + lib/model/model.go | 10 +++---- + lib/model/model_test.go | 2 +- + lib/protocol/benchmark_test.go | 5 ++-- + lib/protocol/encryption.go | 5 +--- + lib/protocol/protocol.go | 42 +++++++++++++++++---------- + lib/protocol/protocol_test.go | 22 +++++++------- + lib/testutils/testutils.go | 47 ++++++++++++++++++++++++++++++ + 11 files changed, 106 insertions(+), 124 deletions(-) + +--- a/lib/api/mocked_model_test.go ++++ b/lib/api/mocked_model_test.go +@@ -11,7 +11,6 @@ + "net" + "time" + +- "github.com/syncthing/syncthing/lib/connections" + "github.com/syncthing/syncthing/lib/db" + "github.com/syncthing/syncthing/lib/model" + "github.com/syncthing/syncthing/lib/protocol" +@@ -114,7 +113,7 @@ + + func (m *mockedModel) BringToFront(folder, file string) {} + +-func (m *mockedModel) Connection(deviceID protocol.DeviceID) (connections.Connection, bool) { ++func (m *mockedModel) Connection(deviceID protocol.DeviceID) (protocol.Connection, bool) { + return nil, false + } + +@@ -157,7 +156,7 @@ + return nil + } + +-func (m *mockedModel) AddConnection(conn connections.Connection, hello protocol.Hello) {} ++func (m *mockedModel) AddConnection(conn protocol.Connection, hello protocol.Hello) {} + + func (m *mockedModel) OnHello(protocol.DeviceID, net.Addr, protocol.Hello) error { + return nil +--- a/lib/connections/service.go ++++ b/lib/connections/service.go +@@ -325,15 +325,14 @@ + var protoConn protocol.Connection + passwords := s.cfg.FolderPasswords(remoteID) + if len(passwords) > 0 { +- protoConn = protocol.NewEncryptedConnection(passwords, remoteID, rd, wr, s.model, c.String(), deviceCfg.Compression) ++ protoConn = protocol.NewEncryptedConnection(passwords, remoteID, rd, wr, c, s.model, c, deviceCfg.Compression) + } else { +- protoConn = protocol.NewConnection(remoteID, rd, wr, s.model, c.String(), deviceCfg.Compression) ++ protoConn = protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression) + } +- modelConn := completeConn{c, protoConn} + + l.Infof("Established secure connection to %s at %s", remoteID, c) + +- s.model.AddConnection(modelConn, hello) ++ s.model.AddConnection(protoConn, hello) + continue + } + return nil +--- a/lib/connections/structs.go ++++ b/lib/connections/structs.go +@@ -22,31 +22,6 @@ + "github.com/thejerf/suture/v4" + ) + +-// Connection is what we expose to the outside. It is a protocol.Connection +-// that can be closed and has some metadata. +-type Connection interface { +- protocol.Connection +- Type() string +- Transport() string +- RemoteAddr() net.Addr +- Priority() int +- String() string +- Crypto() string +-} +- +-// completeConn is the aggregation of an internalConn and the +-// protocol.Connection running on top of it. It implements the Connection +-// interface. +-type completeConn struct { +- internalConn +- protocol.Connection +-} +- +-func (c completeConn) Close(err error) { +- c.Connection.Close(err) +- c.internalConn.Close() +-} +- + type tlsConn interface { + io.ReadWriteCloser + ConnectionState() tls.ConnectionState +@@ -107,12 +82,12 @@ + } + } + +-func (c internalConn) Close() { ++func (c internalConn) Close() error { + // *tls.Conn.Close() does more than it says on the tin. Specifically, it + // sends a TLS alert message, which might block forever if the + // connection is dead and we don't have a deadline set. + _ = c.SetWriteDeadline(time.Now().Add(250 * time.Millisecond)) +- _ = c.tlsConn.Close() ++ return c.tlsConn.Close() + } + + func (c internalConn) Type() string { +@@ -203,8 +178,8 @@ + + type Model interface { + protocol.Model +- AddConnection(conn Connection, hello protocol.Hello) +- Connection(remoteID protocol.DeviceID) (Connection, bool) ++ AddConnection(conn protocol.Connection, hello protocol.Hello) ++ Connection(remoteID protocol.DeviceID) (protocol.Connection, bool) + OnHello(protocol.DeviceID, net.Addr, protocol.Hello) error + GetHello(protocol.DeviceID) protocol.HelloIntf + } +--- a/lib/model/fakeconns_test.go ++++ b/lib/model/fakeconns_test.go +@@ -9,13 +9,12 @@ + import ( + "bytes" + "context" +- "net" + "sync" + "time" + +- "github.com/syncthing/syncthing/lib/connections" + "github.com/syncthing/syncthing/lib/protocol" + "github.com/syncthing/syncthing/lib/scanner" ++ "github.com/syncthing/syncthing/lib/testutils" + ) + + type downloadProgressMessage struct { +@@ -24,7 +23,7 @@ + } + + type fakeConnection struct { +- fakeUnderlyingConn ++ testutils.FakeConnectionInfo + id protocol.DeviceID + downloadProgressMessages []downloadProgressMessage + closed bool +@@ -219,50 +218,3 @@ + + return fc + } +- +-type fakeProtoConn struct { +- protocol.Connection +- fakeUnderlyingConn +-} +- +-func newFakeProtoConn(protoConn protocol.Connection) connections.Connection { +- return &fakeProtoConn{Connection: protoConn} +-} +- +-// fakeUnderlyingConn implements the methods of connections.Connection that are +-// not implemented by protocol.Connection +-type fakeUnderlyingConn struct{} +- +-func (f *fakeUnderlyingConn) RemoteAddr() net.Addr { +- return &fakeAddr{} +-} +- +-func (f *fakeUnderlyingConn) Type() string { +- return "fake" +-} +- +-func (f *fakeUnderlyingConn) Crypto() string { +- return "fake" +-} +- +-func (f *fakeUnderlyingConn) Transport() string { +- return "fake" +-} +- +-func (f *fakeUnderlyingConn) Priority() int { +- return 9000 +-} +- +-func (f *fakeUnderlyingConn) String() string { +- return "" +-} +- +-type fakeAddr struct{} +- +-func (fakeAddr) Network() string { +- return "network" +-} +- +-func (fakeAddr) String() string { +- return "address" +-} +--- a/lib/model/model.go ++++ b/lib/model/model.go +@@ -147,7 +147,7 @@ + + // fields protected by pmut + pmut sync.RWMutex +- conn map[protocol.DeviceID]connections.Connection ++ conn map[protocol.DeviceID]protocol.Connection + connRequestLimiters map[protocol.DeviceID]*byteSemaphore + closed map[protocol.DeviceID]chan struct{} + helloMessages map[protocol.DeviceID]protocol.Hello +@@ -232,7 +232,7 @@ + + // fields protected by pmut + pmut: sync.NewRWMutex(), +- conn: make(map[protocol.DeviceID]connections.Connection), ++ conn: make(map[protocol.DeviceID]protocol.Connection), + connRequestLimiters: make(map[protocol.DeviceID]*byteSemaphore), + closed: make(map[protocol.DeviceID]chan struct{}), + helloMessages: make(map[protocol.DeviceID]protocol.Hello), +@@ -1653,7 +1653,7 @@ + + m.progressEmitter.temporaryIndexUnsubscribe(conn) + +- l.Infof("Connection to %s at %s closed: %v", device, conn.Name(), err) ++ l.Infof("Connection to %s at %s closed: %v", device, conn, err) + m.evLogger.Log(events.DeviceDisconnected, map[string]string{ + "id": device.String(), + "error": err.Error(), +@@ -1905,7 +1905,7 @@ + } + + // Connection returns the current connection for device, and a boolean whether a connection was found. +-func (m *model) Connection(deviceID protocol.DeviceID) (connections.Connection, bool) { ++func (m *model) Connection(deviceID protocol.DeviceID) (protocol.Connection, bool) { + m.pmut.RLock() + cn, ok := m.conn[deviceID] + m.pmut.RUnlock() +@@ -2031,7 +2031,7 @@ + // AddConnection adds a new peer connection to the model. An initial index will + // be sent to the connected peer, thereafter index updates whenever the local + // folder changes. +-func (m *model) AddConnection(conn connections.Connection, hello protocol.Hello) { ++func (m *model) AddConnection(conn protocol.Connection, hello protocol.Hello) { + deviceID := conn.ID() + device, ok := m.cfg.Device(deviceID) + if !ok { +--- a/lib/model/model_test.go ++++ b/lib/model/model_test.go +@@ -3298,7 +3298,7 @@ + + br := &testutils.BlockingRW{} + nw := &testutils.NoopRW{} +- m.AddConnection(newFakeProtoConn(protocol.NewConnection(device1, br, nw, m, "testConn", protocol.CompressionNever)), protocol.Hello{}) ++ m.AddConnection(protocol.NewConnection(device1, br, nw, testutils.NoopCloser{}, m, &testutils.FakeConnectionInfo{"fc"}, protocol.CompressionNever), protocol.Hello{}) + m.pmut.RLock() + if len(m.closed) != 1 { + t.Fatalf("Expected just one conn (len(m.conn) == %v)", len(m.conn)) +--- a/lib/protocol/benchmark_test.go ++++ b/lib/protocol/benchmark_test.go +@@ -10,6 +10,7 @@ + "testing" + + "github.com/syncthing/syncthing/lib/dialer" ++ "github.com/syncthing/syncthing/lib/testutils" + ) + + func BenchmarkRequestsRawTCP(b *testing.B) { +@@ -59,9 +60,9 @@ + + func benchmarkRequestsConnPair(b *testing.B, conn0, conn1 net.Conn) { + // Start up Connections on them +- c0 := NewConnection(LocalDeviceID, conn0, conn0, new(fakeModel), "c0", CompressionMetadata) ++ c0 := NewConnection(LocalDeviceID, conn0, conn0, testutils.NoopCloser{}, new(fakeModel), &testutils.FakeConnectionInfo{"c0"}, CompressionMetadata) + c0.Start() +- c1 := NewConnection(LocalDeviceID, conn1, conn1, new(fakeModel), "c1", CompressionMetadata) ++ c1 := NewConnection(LocalDeviceID, conn1, conn1, testutils.NoopCloser{}, new(fakeModel), &testutils.FakeConnectionInfo{"c1"}, CompressionMetadata) + c1.Start() + + // Satisfy the assertions in the protocol by sending an initial cluster config +--- a/lib/protocol/encryption.go ++++ b/lib/protocol/encryption.go +@@ -128,6 +128,7 @@ + // The encryptedConnection sits between the model and the encrypted device. It + // encrypts outgoing metadata and decrypts incoming responses. + type encryptedConnection struct { ++ ConnectionInfo + conn Connection + folderKeys map[string]*[keySize]byte // folder ID -> key + } +@@ -140,10 +141,6 @@ + return e.conn.ID() + } + +-func (e encryptedConnection) Name() string { +- return e.conn.Name() +-} +- + func (e encryptedConnection) Index(ctx context.Context, folder string, files []FileInfo) error { + if folderKey, ok := e.folderKeys[folder]; ok { + encryptFileInfos(files, folderKey) +--- a/lib/protocol/protocol.go ++++ b/lib/protocol/protocol.go +@@ -8,6 +8,7 @@ + "encoding/binary" + "fmt" + "io" ++ "net" + "path" + "strings" + "sync" +@@ -134,7 +135,6 @@ + Start() + Close(err error) + ID() DeviceID +- Name() string + Index(ctx context.Context, folder string, files []FileInfo) error + IndexUpdate(ctx context.Context, folder string, files []FileInfo) error + Request(ctx context.Context, folder string, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) +@@ -142,16 +142,28 @@ + DownloadProgress(ctx context.Context, folder string, updates []FileDownloadProgressUpdate) + Statistics() Statistics + Closed() bool ++ ConnectionInfo ++} ++ ++type ConnectionInfo interface { ++ Type() string ++ Transport() string ++ RemoteAddr() net.Addr ++ Priority() int ++ String() string ++ Crypto() string + } + + type rawConnection struct { ++ ConnectionInfo ++ + id DeviceID +- name string + receiver Model + startTime time.Time + +- cr *countingReader +- cw *countingWriter ++ cr *countingReader ++ cw *countingWriter ++ closer io.Closer // Closing the underlying connection and thus cr and cw + + awaiting map[int]chan asyncResult + awaitingMut sync.Mutex +@@ -205,13 +217,13 @@ + // Should not be modified in production code, just for testing. + var CloseTimeout = 10 * time.Second + +-func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress Compression) Connection { ++func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver Model, connInfo ConnectionInfo, compress Compression) Connection { + receiver = nativeModel{receiver} +- rc := newRawConnection(deviceID, reader, writer, receiver, name, compress) ++ rc := newRawConnection(deviceID, reader, writer, closer, receiver, connInfo, compress) + return wireFormatConnection{rc} + } + +-func NewEncryptedConnection(passwords map[string]string, deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress Compression) Connection { ++func NewEncryptedConnection(passwords map[string]string, deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver Model, connInfo ConnectionInfo, compress Compression) Connection { + keys := keysFromPasswords(passwords) + + // Encryption / decryption is first (outermost) before conversion to +@@ -221,23 +233,24 @@ + + // We do the wire format conversion first (outermost) so that the + // metadata is in wire format when it reaches the encryption step. +- rc := newRawConnection(deviceID, reader, writer, em, name, compress) +- ec := encryptedConnection{conn: rc, folderKeys: keys} ++ rc := newRawConnection(deviceID, reader, writer, closer, em, connInfo, compress) ++ ec := encryptedConnection{ConnectionInfo: rc, conn: rc, folderKeys: keys} + wc := wireFormatConnection{ec} + + return wc + } + +-func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress Compression) *rawConnection { ++func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver Model, connInfo ConnectionInfo, compress Compression) *rawConnection { + cr := &countingReader{Reader: reader} + cw := &countingWriter{Writer: writer} + + return &rawConnection{ ++ ConnectionInfo: connInfo, + id: deviceID, +- name: name, + receiver: receiver, + cr: cr, + cw: cw, ++ closer: closer, + awaiting: make(map[int]chan asyncResult), + inbox: make(chan message), + outbox: make(chan asyncMessage), +@@ -282,10 +295,6 @@ + return c.id + } + +-func (c *rawConnection) Name() string { +- return c.name +-} +- + // Index writes the list of file information to the connected peer device + func (c *rawConnection) Index(ctx context.Context, folder string, idx []FileInfo) error { + select { +@@ -931,6 +940,9 @@ + func (c *rawConnection) internalClose(err error) { + c.closeOnce.Do(func() { + l.Debugln("close due to", err) ++ if cerr := c.closer.Close(); cerr != nil { ++ l.Debugln(c.id, "failed to close underlying conn:", cerr) ++ } + close(c.closed) + + c.awaitingMut.Lock() +--- a/lib/protocol/protocol_test.go ++++ b/lib/protocol/protocol_test.go +@@ -31,10 +31,10 @@ + ar, aw := io.Pipe() + br, bw := io.Pipe() + +- c0 := NewConnection(c0ID, ar, bw, newTestModel(), "name", CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) ++ c0 := NewConnection(c0ID, ar, bw, testutils.NoopCloser{}, newTestModel(), &testutils.FakeConnectionInfo{"name"}, CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) + c0.Start() + defer closeAndWait(c0, ar, bw) +- c1 := NewConnection(c1ID, br, aw, newTestModel(), "name", CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) ++ c1 := NewConnection(c1ID, br, aw, testutils.NoopCloser{}, newTestModel(), &testutils.FakeConnectionInfo{"name"}, CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) + c1.Start() + defer closeAndWait(c1, ar, bw) + c0.ClusterConfig(ClusterConfig{}) +@@ -57,10 +57,10 @@ + ar, aw := io.Pipe() + br, bw := io.Pipe() + +- c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) ++ c0 := NewConnection(c0ID, ar, bw, testutils.NoopCloser{}, m0, &testutils.FakeConnectionInfo{"name"}, CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) + c0.Start() + defer closeAndWait(c0, ar, bw) +- c1 := NewConnection(c1ID, br, aw, m1, "name", CompressionAlways) ++ c1 := NewConnection(c1ID, br, aw, testutils.NoopCloser{}, m1, &testutils.FakeConnectionInfo{"name"}, CompressionAlways) + c1.Start() + defer closeAndWait(c1, ar, bw) + c0.ClusterConfig(ClusterConfig{}) +@@ -102,7 +102,7 @@ + m := newTestModel() + + rw := testutils.NewBlockingRW() +- c := NewConnection(c0ID, rw, rw, m, "name", CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) ++ c := NewConnection(c0ID, rw, rw, testutils.NoopCloser{}, m, &testutils.FakeConnectionInfo{"name"}, CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) + c.Start() + defer closeAndWait(c, rw) + +@@ -153,10 +153,10 @@ + ar, aw := io.Pipe() + br, bw := io.Pipe() + +- c0 := NewConnection(c0ID, ar, bw, m0, "c0", CompressionNever).(wireFormatConnection).Connection.(*rawConnection) ++ c0 := NewConnection(c0ID, ar, bw, testutils.NoopCloser{}, m0, &testutils.FakeConnectionInfo{"c0"}, CompressionNever).(wireFormatConnection).Connection.(*rawConnection) + c0.Start() + defer closeAndWait(c0, ar, bw) +- c1 := NewConnection(c1ID, br, aw, m1, "c1", CompressionNever) ++ c1 := NewConnection(c1ID, br, aw, testutils.NoopCloser{}, m1, &testutils.FakeConnectionInfo{"c1"}, CompressionNever) + c1.Start() + defer closeAndWait(c1, ar, bw) + c0.ClusterConfig(ClusterConfig{}) +@@ -193,7 +193,7 @@ + m := newTestModel() + + rw := testutils.NewBlockingRW() +- c := NewConnection(c0ID, rw, &testutils.NoopRW{}, m, "name", CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) ++ c := NewConnection(c0ID, rw, &testutils.NoopRW{}, testutils.NoopCloser{}, m, &testutils.FakeConnectionInfo{"name"}, CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) + c.Start() + defer closeAndWait(c, rw) + +@@ -245,7 +245,7 @@ + m := newTestModel() + + rw := testutils.NewBlockingRW() +- c := NewConnection(c0ID, rw, rw, m, "name", CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) ++ c := NewConnection(c0ID, rw, rw, testutils.NoopCloser{}, m, &testutils.FakeConnectionInfo{"name"}, CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) + c.Start() + defer closeAndWait(c, rw) + +@@ -865,7 +865,7 @@ + m := newTestModel() + + rw := testutils.NewBlockingRW() +- c := NewConnection(c0ID, rw, rw, m, "name", CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) ++ c := NewConnection(c0ID, rw, rw, testutils.NoopCloser{}, m, &testutils.FakeConnectionInfo{"name"}, CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) + c.Start() + defer closeAndWait(c, rw) + +@@ -889,7 +889,7 @@ + // the model callbacks (ClusterConfig). + m := newTestModel() + rw := testutils.NewBlockingRW() +- c := NewConnection(c0ID, rw, &testutils.NoopRW{}, m, "name", CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) ++ c := NewConnection(c0ID, rw, &testutils.NoopRW{}, testutils.NoopCloser{}, m, &testutils.FakeConnectionInfo{"name"}, CompressionAlways).(wireFormatConnection).Connection.(*rawConnection) + m.ccFn = func(devID DeviceID, cc ClusterConfig) { + c.Close(errManual) + } +--- a/lib/testutils/testutils.go ++++ b/lib/testutils/testutils.go +@@ -8,6 +8,7 @@ + + import ( + "errors" ++ "net" + "sync" + ) + +@@ -52,3 +53,49 @@ + func (rw *NoopRW) Write(p []byte) (n int, err error) { + return len(p), nil + } ++ ++type NoopCloser struct{} ++ ++func (NoopCloser) Close() error { ++ return nil ++} ++ ++// FakeConnectionInfo implements the methods of protocol.Connection that are ++// not implemented by protocol.Connection ++type FakeConnectionInfo struct { ++ Name string ++} ++ ++func (f *FakeConnectionInfo) RemoteAddr() net.Addr { ++ return &FakeAddr{} ++} ++ ++func (f *FakeConnectionInfo) Type() string { ++ return "fake" ++} ++ ++func (f *FakeConnectionInfo) Crypto() string { ++ return "fake" ++} ++ ++func (f *FakeConnectionInfo) Transport() string { ++ return "fake" ++} ++ ++func (f *FakeConnectionInfo) Priority() int { ++ return 9000 ++} ++ ++func (f *FakeConnectionInfo) String() string { ++ return "" ++} ++ ++type FakeAddr struct{} ++ ++func (FakeAddr) Network() string { ++ return "network" ++} ++ ++func (FakeAddr) String() string { ++ return "address" ++} diff -Nru syncthing-1.12.1~ds1/debian/patches/0003-lib-model-Handle-index-sender-terminating-due-to-err.patch syncthing-1.12.1~ds1/debian/patches/0003-lib-model-Handle-index-sender-terminating-due-to-err.patch --- syncthing-1.12.1~ds1/debian/patches/0003-lib-model-Handle-index-sender-terminating-due-to-err.patch 1969-12-31 19:00:00.000000000 -0500 +++ syncthing-1.12.1~ds1/debian/patches/0003-lib-model-Handle-index-sender-terminating-due-to-err.patch 2021-07-15 11:47:47.000000000 -0400 @@ -0,0 +1,62 @@ +From 6eed8ef5f0e253878221546afe395af103b1d43b Mon Sep 17 00:00:00 2001 +From: Simon Frei +Date: Wed, 30 Dec 2020 09:59:11 +0100 +Subject: [PATCH 3/4] lib/model: Handle index sender terminating due to error + (fixes #7231) (#7232) + +--- + lib/model/indexsender.go | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +--- a/lib/model/indexsender.go ++++ b/lib/model/indexsender.go +@@ -30,6 +30,7 @@ + prevSequence int64 + evLogger events.Logger + connClosed chan struct{} ++ done chan struct{} + token suture.ServiceToken + pauseChan chan struct{} + resumeChan chan *db.FileSet +@@ -38,6 +39,7 @@ + func (s *indexSender) Serve(ctx context.Context) (err error) { + l.Debugf("Starting indexSender for %s to %s at %s (slv=%d)", s.folder, s.conn.ID(), s.conn, s.prevSequence) + defer func() { ++ close(s.done) + err = util.NoRestartErr(err) + l.Debugf("Exiting indexSender for %s to %s at %s: %v", s.folder, s.conn.ID(), s.conn, err) + }() +@@ -101,14 +103,14 @@ + + func (s *indexSender) resume(fset *db.FileSet) { + select { +- case <-s.connClosed: ++ case <-s.done: + case s.resumeChan <- fset: + } + } + + func (s *indexSender) pause() { + select { +- case <-s.connClosed: ++ case <-s.done: + case s.pauseChan <- struct{}{}: + } + } +@@ -314,6 +316,7 @@ + is := &indexSender{ + conn: r.conn, + connClosed: r.closed, ++ done: make(chan struct{}), + folder: folder.ID, + folderIsReceiveEncrypted: folder.Type == config.FolderTypeReceiveEncrypted, + fset: fset, +@@ -366,7 +369,7 @@ + delete(r.indexSenders, folder) + } + } +- for folder := range r.indexSenders { ++ for folder := range r.startInfos { + if _, ok := except[folder]; !ok { + delete(r.startInfos, folder) + } diff -Nru syncthing-1.12.1~ds1/debian/patches/0004-lib-db-Fix-and-improve-removing-entries-from-global-.patch syncthing-1.12.1~ds1/debian/patches/0004-lib-db-Fix-and-improve-removing-entries-from-global-.patch --- syncthing-1.12.1~ds1/debian/patches/0004-lib-db-Fix-and-improve-removing-entries-from-global-.patch 1969-12-31 19:00:00.000000000 -0500 +++ syncthing-1.12.1~ds1/debian/patches/0004-lib-db-Fix-and-improve-removing-entries-from-global-.patch 2021-07-15 11:47:47.000000000 -0400 @@ -0,0 +1,120 @@ +From 631cf607495f4a21c72c731f3fb54bbeacd9af15 Mon Sep 17 00:00:00 2001 +From: Simon Frei +Date: Mon, 8 Feb 2021 08:38:41 +0100 +Subject: [PATCH 4/4] lib/db: Fix and improve removing entries from global (ref + #6501) (#7336) + +--- + lib/db/db_test.go | 43 ++++++++++++++++++++++++++++++++++++++++++ + lib/db/transactions.go | 27 ++++++++++++++++---------- + 2 files changed, 60 insertions(+), 10 deletions(-) + +--- a/lib/db/db_test.go ++++ b/lib/db/db_test.go +@@ -942,6 +942,49 @@ + } + } + ++func TestNeedAfterDropGlobal(t *testing.T) { ++ db := NewLowlevel(backend.OpenMemory()) ++ defer db.Close() ++ ++ folder := "test" ++ testFs := fs.NewFilesystem(fs.FilesystemTypeFake, "") ++ ++ fs := NewFileSet(folder, testFs, db) ++ ++ // Initial: ++ // Three devices and a file "test": local has Version 1, remoteDevice0 ++ // Version 2 and remoteDevice2 doesn't have it. ++ // All of them have "bar", just so the db knows about remoteDevice2. ++ files := []protocol.FileInfo{ ++ {Name: "foo", Version: protocol.Vector{}.Update(myID), Sequence: 1}, ++ {Name: "bar", Version: protocol.Vector{}.Update(myID), Sequence: 2}, ++ } ++ fs.Update(protocol.LocalDeviceID, files) ++ files[0].Version = files[0].Version.Update(myID) ++ fs.Update(remoteDevice0, files) ++ fs.Update(remoteDevice1, files[1:]) ++ ++ // remoteDevice1 needs one file: test ++ snap := fs.Snapshot() ++ c := snap.NeedSize(remoteDevice1) ++ if c.Files != 1 { ++ t.Errorf("Expected 1 needed files initially, got %v", c.Files) ++ } ++ snap.Release() ++ ++ // Drop remoteDevice0, i.e. remove all their files from db. ++ // That changes the global file, which is now what local has. ++ fs.Drop(remoteDevice0) ++ ++ // remoteDevice1 still needs test. ++ snap = fs.Snapshot() ++ c = snap.NeedSize(remoteDevice1) ++ if c.Files != 1 { ++ t.Errorf("Expected still 1 needed files, got %v", c.Files) ++ } ++ snap.Release() ++} ++ + func numBlockLists(db *Lowlevel) (int, error) { + it, err := db.Backend.NewPrefixIterator([]byte{KeyTypeBlockList}) + if err != nil { +--- a/lib/db/transactions.go ++++ b/lib/db/transactions.go +@@ -813,11 +813,11 @@ + } + + var global protocol.FileIntf +- var gotGlobal, ok bool ++ var gotGlobal bool + +- globalFV, ok := fl.GetGlobal() ++ globalFV, haveGlobal := fl.GetGlobal() + // Add potential needs of the removed device +- if ok && !globalFV.IsInvalid() && Need(globalFV, false, protocol.Vector{}) && !Need(oldGlobalFV, haveRemoved, removedFV.Version) { ++ if haveGlobal && !globalFV.IsInvalid() && Need(globalFV, false, protocol.Vector{}) && !Need(oldGlobalFV, haveRemoved, removedFV.Version) { + keyBuf, global, _, err = t.getGlobalFromVersionList(keyBuf, folder, file, true, fl) + if err != nil { + return nil, err +@@ -840,16 +840,23 @@ + return keyBuf, nil + } + +- var f protocol.FileIntf +- keyBuf, f, err = t.getGlobalFromFileVersion(keyBuf, folder, file, true, oldGlobalFV) ++ var oldGlobal protocol.FileIntf ++ keyBuf, oldGlobal, err = t.getGlobalFromFileVersion(keyBuf, folder, file, true, oldGlobalFV) + if err != nil { + return nil, err + } +- meta.removeFile(protocol.GlobalDeviceID, f) ++ meta.removeFile(protocol.GlobalDeviceID, oldGlobal) + + // Remove potential device needs +- if fv, have := fl.Get(protocol.LocalDeviceID[:]); Need(removedFV, have, fv.Version) { +- meta.removeNeeded(protocol.LocalDeviceID, f) ++ shouldRemoveNeed := func(dev protocol.DeviceID) bool { ++ fv, have := fl.Get(dev[:]) ++ if !Need(oldGlobalFV, have, fv.Version) { ++ return false // Didn't need it before ++ } ++ return !haveGlobal || !Need(globalFV, have, fv.Version) ++ } ++ if shouldRemoveNeed(protocol.LocalDeviceID) { ++ meta.removeNeeded(protocol.LocalDeviceID, oldGlobal) + if keyBuf, err = t.updateLocalNeed(keyBuf, folder, file, false); err != nil { + return nil, err + } +@@ -858,8 +865,8 @@ + if bytes.Equal(dev[:], device) { // Was the previous global + continue + } +- if fv, have := fl.Get(dev[:]); Need(removedFV, have, fv.Version) { +- meta.removeNeeded(dev, f) ++ if shouldRemoveNeed(dev) { ++ meta.removeNeeded(dev, oldGlobal) + } + } + diff -Nru syncthing-1.12.1~ds1/debian/patches/CVE-2021-21404.patch syncthing-1.12.1~ds1/debian/patches/CVE-2021-21404.patch --- syncthing-1.12.1~ds1/debian/patches/CVE-2021-21404.patch 1969-12-31 19:00:00.000000000 -0500 +++ syncthing-1.12.1~ds1/debian/patches/CVE-2021-21404.patch 2021-07-15 11:47:47.000000000 -0400 @@ -0,0 +1,20 @@ +--- a/lib/relay/protocol/protocol.go ++++ b/lib/relay/protocol/protocol.go +@@ -4,6 +4,7 @@ + + import ( + "errors" ++ "fmt" + "io" + ) + +@@ -86,6 +87,9 @@ + if header.magic != magic { + return nil, errors.New("magic mismatch") + } ++ if header.messageLength < 0 || header.messageLength > 1024 { ++ return nil, fmt.Errorf("bad length (%d)", header.messageLength) ++ } + + buf = make([]byte, int(header.messageLength)) + if _, err := io.ReadFull(r, buf); err != nil { diff -Nru syncthing-1.12.1~ds1/debian/patches/series syncthing-1.12.1~ds1/debian/patches/series --- syncthing-1.12.1~ds1/debian/patches/series 2021-02-04 11:26:39.000000000 -0500 +++ syncthing-1.12.1~ds1/debian/patches/series 2021-07-15 11:47:47.000000000 -0400 @@ -6,3 +6,8 @@ tests-wait-longer.patch skip-failing-test.patch badger-remove.patch +CVE-2021-21404.patch +0004-lib-db-Fix-and-improve-removing-entries-from-global-.patch +0003-lib-model-Handle-index-sender-terminating-due-to-err.patch +0001-lib-db-Prevent-IndexID-creation-race-7211.patch +0002-lib-Close-underlying-conn-in-protocol-fixes-7165-721.patch