From 0db80710aaf99ce9904f689cae8beeb0fcdaed98 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Fri, 1 Jan 2016 20:11:12 +0100 Subject: [PATCH] Detect nonstandard hash algo and stop folder (ref #2314) --- lib/model/model.go | 43 ++++++++++++++++++++---- lib/protocol/hashalgorithm.go | 54 ++++++++++++++++++++++++++++++ lib/protocol/hashalgorithm_test.go | 35 +++++++++++++++++++ lib/protocol/protocol.go | 7 ++++ 4 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 lib/protocol/hashalgorithm.go create mode 100644 lib/protocol/hashalgorithm_test.go diff --git a/lib/model/model.go b/lib/model/model.go index 50c9d466f..83f091e62 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -520,11 +520,7 @@ func (m *Model) Index(deviceID protocol.DeviceID, folder string, fs []protocol.F l.Debugf("IDX(in): %s %q: %d files", deviceID, folder, len(fs)) if !m.folderSharedWith(folder, deviceID) { - events.Default.Log(events.FolderRejected, map[string]string{ - "folder": folder, - "device": deviceID.String(), - }) - l.Infof("Unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder, deviceID) + l.Debugf("Unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder, deviceID) return } @@ -566,7 +562,7 @@ func (m *Model) IndexUpdate(deviceID protocol.DeviceID, folder string, fs []prot l.Debugf("%v IDXUP(in): %s / %q: %d files", m, deviceID, folder, len(fs)) if !m.folderSharedWith(folder, deviceID) { - l.Infof("Update for unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder, deviceID) + l.Debugf("Update for unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder, deviceID) return } @@ -596,6 +592,10 @@ func (m *Model) IndexUpdate(deviceID protocol.DeviceID, folder string, fs []prot func (m *Model) folderSharedWith(folder string, deviceID protocol.DeviceID) bool { m.fmut.RLock() defer m.fmut.RUnlock() + return m.folderSharedWithUnlocked(folder, deviceID) +} + +func (m *Model) folderSharedWithUnlocked(folder string, deviceID protocol.DeviceID) bool { for _, nfolder := range m.deviceFolders[deviceID] { if nfolder == folder { return true @@ -629,6 +629,37 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon m.pmut.Unlock() + // Check the peer device's announced folders against our own. Emits events + // for folders that we don't expect (unknown or not shared). + + m.fmut.Lock() +nextFolder: + for _, folder := range cm.Folders { + cfg := m.folderCfgs[folder.ID] + + if _, err := protocol.HashAlgorithmFromFlagBits(folder.Flags); err != nil { + // The hash algorithm failed to deserialize, so it's not SHA256 + // (the only acceptable algorithm). + l.Warnf("Device %v: %v", deviceID, err) + cfg.Invalid = err.Error() + " from " + deviceID.String() + m.cfg.SetFolder(cfg) + if srv := m.folderRunners[folder.ID]; srv != nil { + srv.setError(fmt.Errorf(cfg.Invalid)) + } + continue nextFolder + } + + if !m.folderSharedWithUnlocked(folder.ID, deviceID) { + events.Default.Log(events.FolderRejected, map[string]string{ + "folder": folder.ID, + "device": deviceID.String(), + }) + l.Infof("Unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.ID, deviceID) + continue + } + } + m.fmut.Unlock() + events.Default.Log(events.DeviceConnected, event) l.Infof(`Device %s client is "%s %s" named "%s"`, deviceID, cm.ClientName, cm.ClientVersion, cm.DeviceName) diff --git a/lib/protocol/hashalgorithm.go b/lib/protocol/hashalgorithm.go new file mode 100644 index 000000000..07d33116b --- /dev/null +++ b/lib/protocol/hashalgorithm.go @@ -0,0 +1,54 @@ +// Copyright (C) 2016 The Protocol Authors. + +package protocol + +import "fmt" + +type HashAlgorithm int + +const ( + SHA256 HashAlgorithm = iota +) + +func (h HashAlgorithm) String() string { + switch h { + case SHA256: + return "sha256" + default: + return "unknown" + } +} + +// FlagBits returns the bits that we should or into the folder flag field to +// indicate the hash algorithm. +func (h HashAlgorithm) FlagBits() uint32 { + switch h { + case SHA256: + return FolderHashSHA256 << FolderHashShiftBits + default: + panic("unknown hash algorithm") + } +} + +func (h *HashAlgorithm) UnmarshalText(bs []byte) error { + switch string(bs) { + case "sha256": + *h = SHA256 + return nil + } + return fmt.Errorf("Unknown hash algorithm %q", string(bs)) +} + +func (h *HashAlgorithm) MarshalText() ([]byte, error) { + return []byte(h.String()), nil +} + +func HashAlgorithmFromFlagBits(flags uint32) (HashAlgorithm, error) { + algo := flags >> FolderHashShiftBits & FolderHashMask + switch algo { + case FolderHashSHA256: + return SHA256, nil + default: + return 0, fmt.Errorf("Unknown hash algorithm %d", algo) + } +} diff --git a/lib/protocol/hashalgorithm_test.go b/lib/protocol/hashalgorithm_test.go new file mode 100644 index 000000000..89f13d41d --- /dev/null +++ b/lib/protocol/hashalgorithm_test.go @@ -0,0 +1,35 @@ +// Copyright (C) 2016 The Protocol Authors. + +package protocol + +import "testing" + +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Reserved | Hash |D|P|R| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +func TestHashAlgorithmFromFlagBits(t *testing.T) { + // SHA256 is algorithm zero, shifted three bits to the left (for clarity, + // I know it doesn't actually do anything). + + sha256 := uint32(0 << 3) + + h, err := HashAlgorithmFromFlagBits(sha256) + if err != nil { + t.Error(err) + } + if h != SHA256 { + t.Error("Zero should have unmarshalled as SHA256") + } + + // Any other algorithm is unknown + unknown := uint32(1 << 3) + + _, err = HashAlgorithmFromFlagBits(unknown) + if err == nil { + t.Error("Unknown algo should not have unmarshalled") + } +} diff --git a/lib/protocol/protocol.go b/lib/protocol/protocol.go index 1b89ee097..c859636e8 100644 --- a/lib/protocol/protocol.go +++ b/lib/protocol/protocol.go @@ -66,6 +66,13 @@ const ( FlagFolderReadOnly uint32 = 1 << 0 FlagFolderIgnorePerms = 1 << 1 FlagFolderIgnoreDelete = 1 << 2 + + // The folder hash algorithm IDs, to be put in the flags field by shifting + // left FolderHashShiftBits + FolderHashSHA256 = 0 + // ... 1 through 15 currently reserved + FolderHashMask = 15 + FolderHashShiftBits = 3 ) // ClusterConfigMessage.Folders.Devices flags