mirror of
https://github.com/octoleo/syncthing.git
synced 2025-02-08 14:58:26 +00:00
lib/protocol: Add some consistency checks on incoming index updates (fixes #4053)
With this change we will throw a protocol error on some kinds of malformed index entries. GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4064
This commit is contained in:
parent
1ad547fb65
commit
b75b4190c8
@ -57,6 +57,9 @@ var (
|
|||||||
errUnknownMessage = errors.New("unknown message")
|
errUnknownMessage = errors.New("unknown message")
|
||||||
errInvalidFilename = errors.New("filename is invalid")
|
errInvalidFilename = errors.New("filename is invalid")
|
||||||
errUncleanFilename = errors.New("filename not in canonical format")
|
errUncleanFilename = errors.New("filename not in canonical format")
|
||||||
|
errDeletedHasBlocks = errors.New("deleted file with non-empty block list")
|
||||||
|
errDirectoryHasBlocks = errors.New("directory with non-empty block list")
|
||||||
|
errFileHasNoBlocks = errors.New("file with empty block list")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Model interface {
|
type Model interface {
|
||||||
@ -308,7 +311,7 @@ func (c *rawConnection) readerLoop() (err error) {
|
|||||||
if state != stateReady {
|
if state != stateReady {
|
||||||
return fmt.Errorf("protocol error: index message in state %d", state)
|
return fmt.Errorf("protocol error: index message in state %d", state)
|
||||||
}
|
}
|
||||||
if err := checkFilenames(msg.Files); err != nil {
|
if err := checkIndexConsistency(msg.Files); err != nil {
|
||||||
return fmt.Errorf("protocol error: index: %v", err)
|
return fmt.Errorf("protocol error: index: %v", err)
|
||||||
}
|
}
|
||||||
c.handleIndex(*msg)
|
c.handleIndex(*msg)
|
||||||
@ -319,7 +322,7 @@ func (c *rawConnection) readerLoop() (err error) {
|
|||||||
if state != stateReady {
|
if state != stateReady {
|
||||||
return fmt.Errorf("protocol error: index update message in state %d", state)
|
return fmt.Errorf("protocol error: index update message in state %d", state)
|
||||||
}
|
}
|
||||||
if err := checkFilenames(msg.Files); err != nil {
|
if err := checkIndexConsistency(msg.Files); err != nil {
|
||||||
return fmt.Errorf("protocol error: index update: %v", err)
|
return fmt.Errorf("protocol error: index update: %v", err)
|
||||||
}
|
}
|
||||||
c.handleIndexUpdate(*msg)
|
c.handleIndexUpdate(*msg)
|
||||||
@ -466,15 +469,39 @@ func (c *rawConnection) handleIndexUpdate(im IndexUpdate) {
|
|||||||
c.receiver.IndexUpdate(c.id, im.Folder, im.Files)
|
c.receiver.IndexUpdate(c.id, im.Folder, im.Files)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkFilenames(fs []FileInfo) error {
|
// checkIndexConsistency verifies a number of invariants on FileInfos received in
|
||||||
|
// index messages.
|
||||||
|
func checkIndexConsistency(fs []FileInfo) error {
|
||||||
for _, f := range fs {
|
for _, f := range fs {
|
||||||
if err := checkFilename(f.Name); err != nil {
|
if err := checkFileInfoConsistency(f); err != nil {
|
||||||
return fmt.Errorf("%q: %v", f.Name, err)
|
return fmt.Errorf("%q: %v", f.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkFileInfoConsistency verifies a number of invariants on the given FileInfo
|
||||||
|
func checkFileInfoConsistency(f FileInfo) error {
|
||||||
|
if err := checkFilename(f.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case f.Deleted && len(f.Blocks) != 0:
|
||||||
|
// Deleted files should have no blocks
|
||||||
|
return errDeletedHasBlocks
|
||||||
|
|
||||||
|
case f.Type == FileInfoTypeDirectory && len(f.Blocks) != 0:
|
||||||
|
// Directories should have no blocks
|
||||||
|
return errDirectoryHasBlocks
|
||||||
|
|
||||||
|
case !f.Deleted && f.Type == FileInfoTypeFile && len(f.Blocks) == 0:
|
||||||
|
// Non-deleted files should have at least one block
|
||||||
|
return errFileHasNoBlocks
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// checkFilename verifies that the given filename is valid according to the
|
// checkFilename verifies that the given filename is valid according to the
|
||||||
// spec on what's allowed over the wire. A filename failing this test is
|
// spec on what's allowed over the wire. A filename failing this test is
|
||||||
// grounds for disconnecting the device.
|
// grounds for disconnecting the device.
|
||||||
|
@ -346,3 +346,57 @@ func TestCheckFilename(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckConsistency(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
fi FileInfo
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// valid
|
||||||
|
fi: FileInfo{
|
||||||
|
Name: "foo",
|
||||||
|
Type: FileInfoTypeFile,
|
||||||
|
Blocks: []BlockInfo{{Size: 1234, Offset: 0, Hash: []byte{1, 2, 3, 4}}},
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// deleted with blocks
|
||||||
|
fi: FileInfo{
|
||||||
|
Name: "foo",
|
||||||
|
Deleted: true,
|
||||||
|
Type: FileInfoTypeFile,
|
||||||
|
Blocks: []BlockInfo{{Size: 1234, Offset: 0, Hash: []byte{1, 2, 3, 4}}},
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// no blocks
|
||||||
|
fi: FileInfo{
|
||||||
|
Name: "foo",
|
||||||
|
Type: FileInfoTypeFile,
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// directory with blocks
|
||||||
|
fi: FileInfo{
|
||||||
|
Name: "foo",
|
||||||
|
Type: FileInfoTypeDirectory,
|
||||||
|
Blocks: []BlockInfo{{Size: 1234, Offset: 0, Hash: []byte{1, 2, 3, 4}}},
|
||||||
|
},
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
err := checkFileInfoConsistency(tc.fi)
|
||||||
|
if tc.ok && err != nil {
|
||||||
|
t.Errorf("Unexpected error %v (want nil) for %v", err, tc.fi)
|
||||||
|
}
|
||||||
|
if !tc.ok && err == nil {
|
||||||
|
t.Errorf("Unexpected nil error for %v", tc.fi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user