diff --git a/README.md b/README.md index ad1ccd0f4..79ea84b30 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,9 @@ fingerprint is computed as the SHA-1 hash of the certificate and displayed in BASE32 encoding to form a compact yet convenient string. Currently SHA-1 is deemed secure against preimage attacks. +Incoming requests for file data are verified to the extent that the +requested file name must exist in the local index and the global model. + Installing ========== diff --git a/model/model.go b/model/model.go index 5aed28eb1..bff4427f9 100644 --- a/model/model.go +++ b/model/model.go @@ -13,6 +13,7 @@ acquire locks, but document what locks they require. import ( "crypto/sha1" + "errors" "fmt" "io" "log" @@ -58,6 +59,8 @@ const ( idxBcastMaxDelay = 120 * time.Second // Unless we've already waited this long ) +var ErrNoSuchFile = errors.New("no such file") + // NewModel creates and starts a new model. The model starts in read-only mode, // where it sends index information to connected peers and responds to requests // for file data without altering the local repository in any way. @@ -271,6 +274,16 @@ func (m *Model) Close(node string, err error) { // Request returns the specified data segment by reading it from local disk. // Implements the protocol.Model interface. func (m *Model) Request(nodeID, name string, offset uint64, size uint32, hash []byte) ([]byte, error) { + // Verify that the requested file exists in the local and global model. + m.RLock() + _, localOk := m.local[name] + _, globalOk := m.global[name] + m.RUnlock() + if !localOk || !globalOk { + log.Printf("SECURITY (nonexistent file) REQ(in): %s: %q o=%d s=%d h=%x", nodeID, name, offset, size, hash) + return nil, ErrNoSuchFile + } + if m.trace["net"] && nodeID != "" { log.Printf("NET REQ(in): %s: %q o=%d s=%d h=%x", nodeID, name, offset, size, hash) } diff --git a/model/model_test.go b/model/model_test.go index 31cd2857f..693054e0b 100644 --- a/model/model_test.go +++ b/model/model_test.go @@ -1,6 +1,7 @@ package model import ( + "bytes" "os" "reflect" "testing" @@ -317,3 +318,25 @@ func TestForgetNode(t *testing.T) { t.Errorf("Model len(need) incorrect (%d != %d)", l1, l2) } } + +func TestRequest(t *testing.T) { + m := NewModel("testdata") + fs, _ := m.Walk(false) + m.ReplaceLocal(fs) + + bs, err := m.Request("some node", "foo", 0, 6, nil) + if err != nil { + t.Fatal(err) + } + if bytes.Compare(bs, []byte("foobar")) != 0 { + t.Errorf("Incorrect data from request: %q", string(bs)) + } + + bs, err = m.Request("some node", "../walk.go", 0, 6, nil) + if err == nil { + t.Error("Unexpected nil error on insecure file read") + } + if bs != nil { + t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs)) + } +}