requests_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. // Copyright (C) 2016 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package model
  7. import (
  8. "bytes"
  9. "errors"
  10. "io/ioutil"
  11. "os"
  12. "path/filepath"
  13. "runtime"
  14. "strings"
  15. "testing"
  16. "time"
  17. "github.com/syncthing/syncthing/lib/config"
  18. "github.com/syncthing/syncthing/lib/db"
  19. "github.com/syncthing/syncthing/lib/events"
  20. "github.com/syncthing/syncthing/lib/fs"
  21. "github.com/syncthing/syncthing/lib/ignore"
  22. "github.com/syncthing/syncthing/lib/protocol"
  23. )
  24. func TestRequestSimple(t *testing.T) {
  25. // Verify that the model performs a request and creates a file based on
  26. // an incoming index update.
  27. m, fc, tmpDir := setupModelWithConnection()
  28. defer m.Stop()
  29. defer os.RemoveAll(tmpDir)
  30. // We listen for incoming index updates and trigger when we see one for
  31. // the expected test file.
  32. done := make(chan struct{})
  33. fc.mut.Lock()
  34. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  35. for _, f := range fs {
  36. if f.Name == "testfile" {
  37. close(done)
  38. return
  39. }
  40. }
  41. }
  42. fc.mut.Unlock()
  43. // Send an update for the test file, wait for it to sync and be reported back.
  44. contents := []byte("test file contents\n")
  45. fc.addFile("testfile", 0644, protocol.FileInfoTypeFile, contents)
  46. fc.sendIndexUpdate()
  47. <-done
  48. // Verify the contents
  49. if err := equalContents(filepath.Join(tmpDir, "testfile"), contents); err != nil {
  50. t.Error("File did not sync correctly:", err)
  51. }
  52. }
  53. func TestSymlinkTraversalRead(t *testing.T) {
  54. // Verify that a symlink can not be traversed for reading.
  55. if runtime.GOOS == "windows" {
  56. t.Skip("no symlink support on CI")
  57. return
  58. }
  59. m, fc, tmpDir := setupModelWithConnection()
  60. defer m.Stop()
  61. defer os.RemoveAll(tmpDir)
  62. // We listen for incoming index updates and trigger when we see one for
  63. // the expected test file.
  64. done := make(chan struct{})
  65. fc.mut.Lock()
  66. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  67. for _, f := range fs {
  68. if f.Name == "symlink" {
  69. close(done)
  70. return
  71. }
  72. }
  73. }
  74. fc.mut.Unlock()
  75. // Send an update for the symlink, wait for it to sync and be reported back.
  76. contents := []byte("..")
  77. fc.addFile("symlink", 0644, protocol.FileInfoTypeSymlink, contents)
  78. fc.sendIndexUpdate()
  79. <-done
  80. // Request a file by traversing the symlink
  81. buf := make([]byte, 10)
  82. err := m.Request(device1, "default", "symlink/requests_test.go", 0, nil, 0, false, buf)
  83. if err == nil || !bytes.Equal(buf, make([]byte, 10)) {
  84. t.Error("Managed to traverse symlink")
  85. }
  86. }
  87. func TestSymlinkTraversalWrite(t *testing.T) {
  88. // Verify that a symlink can not be traversed for writing.
  89. if runtime.GOOS == "windows" {
  90. t.Skip("no symlink support on CI")
  91. return
  92. }
  93. m, fc, tmpDir := setupModelWithConnection()
  94. defer m.Stop()
  95. defer os.RemoveAll(tmpDir)
  96. // We listen for incoming index updates and trigger when we see one for
  97. // the expected names.
  98. done := make(chan struct{}, 1)
  99. badReq := make(chan string, 1)
  100. badIdx := make(chan string, 1)
  101. fc.mut.Lock()
  102. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  103. for _, f := range fs {
  104. if f.Name == "symlink" {
  105. done <- struct{}{}
  106. return
  107. }
  108. if strings.HasPrefix(f.Name, "symlink") {
  109. badIdx <- f.Name
  110. return
  111. }
  112. }
  113. }
  114. fc.requestFn = func(folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
  115. if name != "symlink" && strings.HasPrefix(name, "symlink") {
  116. badReq <- name
  117. }
  118. return fc.fileData[name], nil
  119. }
  120. fc.mut.Unlock()
  121. // Send an update for the symlink, wait for it to sync and be reported back.
  122. contents := []byte("..")
  123. fc.addFile("symlink", 0644, protocol.FileInfoTypeSymlink, contents)
  124. fc.sendIndexUpdate()
  125. <-done
  126. // Send an update for things behind the symlink, wait for requests for
  127. // blocks for any of them to come back, or index entries. Hopefully none
  128. // of that should happen.
  129. contents = []byte("testdata testdata\n")
  130. fc.addFile("symlink/testfile", 0644, protocol.FileInfoTypeFile, contents)
  131. fc.addFile("symlink/testdir", 0644, protocol.FileInfoTypeDirectory, contents)
  132. fc.addFile("symlink/testsyml", 0644, protocol.FileInfoTypeSymlink, contents)
  133. fc.sendIndexUpdate()
  134. select {
  135. case name := <-badReq:
  136. t.Fatal("Should not have requested the data for", name)
  137. case name := <-badIdx:
  138. t.Fatal("Should not have sent the index entry for", name)
  139. case <-time.After(3 * time.Second):
  140. // Unfortunately not much else to trigger on here. The puller sleep
  141. // interval is 1s so if we didn't get any requests within two
  142. // iterations we should be fine.
  143. }
  144. }
  145. func TestRequestCreateTmpSymlink(t *testing.T) {
  146. // Test that an update for a temporary file is invalidated
  147. m, fc, tmpDir := setupModelWithConnection()
  148. defer m.Stop()
  149. defer os.RemoveAll(tmpDir)
  150. // We listen for incoming index updates and trigger when we see one for
  151. // the expected test file.
  152. goodIdx := make(chan struct{})
  153. name := fs.TempName("testlink")
  154. fc.mut.Lock()
  155. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  156. for _, f := range fs {
  157. if f.Name == name {
  158. if f.Invalid {
  159. goodIdx <- struct{}{}
  160. } else {
  161. t.Fatal("Received index with non-invalid temporary file")
  162. }
  163. return
  164. }
  165. }
  166. }
  167. fc.mut.Unlock()
  168. // Send an update for the test file, wait for it to sync and be reported back.
  169. fc.addFile(name, 0644, protocol.FileInfoTypeSymlink, []byte(".."))
  170. fc.sendIndexUpdate()
  171. select {
  172. case <-goodIdx:
  173. case <-time.After(3 * time.Second):
  174. t.Fatal("Timed out without index entry being sent")
  175. }
  176. }
  177. func TestRequestVersioningSymlinkAttack(t *testing.T) {
  178. if runtime.GOOS == "windows" {
  179. t.Skip("no symlink support on Windows")
  180. }
  181. // Sets up a folder with trashcan versioning and tries to use a
  182. // deleted symlink to escape
  183. tmpDir := createTmpDir()
  184. defer os.RemoveAll(tmpDir)
  185. cfg := defaultCfgWrapper.RawCopy()
  186. cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpDir)
  187. cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
  188. {DeviceID: device1},
  189. {DeviceID: device2},
  190. }
  191. cfg.Folders[0].Versioning = config.VersioningConfiguration{
  192. Type: "trashcan",
  193. }
  194. w, path := createTmpWrapper(cfg)
  195. defer os.Remove(path)
  196. db := db.OpenMemory()
  197. m := NewModel(w, device1, "syncthing", "dev", db, nil)
  198. m.AddFolder(cfg.Folders[0])
  199. m.ServeBackground()
  200. m.StartFolder("default")
  201. defer m.Stop()
  202. defer os.RemoveAll(tmpDir)
  203. fc := addFakeConn(m, device2)
  204. fc.folder = "default"
  205. // Create a temporary directory that we will use as target to see if
  206. // we can escape to it
  207. tmpdir, err := ioutil.TempDir("", "syncthing-test")
  208. if err != nil {
  209. t.Fatal(err)
  210. }
  211. // We listen for incoming index updates and trigger when we see one for
  212. // the expected test file.
  213. idx := make(chan int)
  214. fc.mut.Lock()
  215. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  216. idx <- len(fs)
  217. }
  218. fc.mut.Unlock()
  219. // Send an update for the test file, wait for it to sync and be reported back.
  220. fc.addFile("foo", 0644, protocol.FileInfoTypeSymlink, []byte(tmpdir))
  221. fc.sendIndexUpdate()
  222. for updates := 0; updates < 1; updates += <-idx {
  223. }
  224. // Delete the symlink, hoping for it to get versioned
  225. fc.deleteFile("foo")
  226. fc.sendIndexUpdate()
  227. for updates := 0; updates < 1; updates += <-idx {
  228. }
  229. // Recreate foo and a file in it with some data
  230. fc.addFile("foo", 0755, protocol.FileInfoTypeDirectory, nil)
  231. fc.addFile("foo/test", 0644, protocol.FileInfoTypeFile, []byte("testtesttest"))
  232. fc.sendIndexUpdate()
  233. for updates := 0; updates < 1; updates += <-idx {
  234. }
  235. // Remove the test file and see if it escaped
  236. fc.deleteFile("foo/test")
  237. fc.sendIndexUpdate()
  238. for updates := 0; updates < 1; updates += <-idx {
  239. }
  240. path = filepath.Join(tmpdir, "test")
  241. if _, err := os.Lstat(path); !os.IsNotExist(err) {
  242. t.Fatal("File escaped to", path)
  243. }
  244. }
  245. func TestPullInvalidIgnoredSO(t *testing.T) {
  246. pullInvalidIgnored(t, config.FolderTypeSendOnly)
  247. }
  248. func TestPullInvalidIgnoredSR(t *testing.T) {
  249. pullInvalidIgnored(t, config.FolderTypeSendReceive)
  250. }
  251. // This test checks that (un-)ignored/invalid/deleted files are treated as expected.
  252. func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
  253. t.Helper()
  254. tmpDir := createTmpDir()
  255. defer os.RemoveAll(tmpDir)
  256. cfg := defaultCfgWrapper.RawCopy()
  257. cfg.Devices = append(cfg.Devices, config.NewDeviceConfiguration(device2, "device2"))
  258. cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpDir)
  259. cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
  260. {DeviceID: device1},
  261. {DeviceID: device2},
  262. }
  263. cfg.Folders[0].Type = ft
  264. m, fc := setupModelWithConnectionManual(cfg)
  265. defer m.Stop()
  266. // Reach in and update the ignore matcher to one that always does
  267. // reloads when asked to, instead of checking file mtimes. This is
  268. // because we might be changing the files on disk often enough that the
  269. // mtimes will be unreliable to determine change status.
  270. m.fmut.Lock()
  271. m.folderIgnores["default"] = ignore.New(cfg.Folders[0].Filesystem(), ignore.WithChangeDetector(newAlwaysChanged()))
  272. m.fmut.Unlock()
  273. if err := m.SetIgnores("default", []string{"*ignored*"}); err != nil {
  274. panic(err)
  275. }
  276. contents := []byte("test file contents\n")
  277. otherContents := []byte("other test file contents\n")
  278. invIgn := "invalid:ignored"
  279. invDel := "invalid:deleted"
  280. ign := "ignoredNonExisting"
  281. ignExisting := "ignoredExisting"
  282. fc.addFile(invIgn, 0644, protocol.FileInfoTypeFile, contents)
  283. fc.addFile(invDel, 0644, protocol.FileInfoTypeFile, contents)
  284. fc.deleteFile(invDel)
  285. fc.addFile(ign, 0644, protocol.FileInfoTypeFile, contents)
  286. fc.addFile(ignExisting, 0644, protocol.FileInfoTypeFile, contents)
  287. if err := ioutil.WriteFile(filepath.Join(tmpDir, ignExisting), otherContents, 0644); err != nil {
  288. panic(err)
  289. }
  290. done := make(chan struct{})
  291. fc.mut.Lock()
  292. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  293. expected := map[string]struct{}{invIgn: {}, ign: {}, ignExisting: {}}
  294. for _, f := range fs {
  295. if _, ok := expected[f.Name]; !ok {
  296. t.Errorf("Unexpected file %v was added to index", f.Name)
  297. }
  298. if !f.Invalid {
  299. t.Errorf("File %v wasn't marked as invalid", f.Name)
  300. }
  301. delete(expected, f.Name)
  302. }
  303. for name := range expected {
  304. t.Errorf("File %v wasn't added to index", name)
  305. }
  306. done <- struct{}{}
  307. }
  308. fc.mut.Unlock()
  309. sub := events.Default.Subscribe(events.FolderErrors)
  310. defer events.Default.Unsubscribe(sub)
  311. fc.sendIndexUpdate()
  312. timeout := time.NewTimer(5 * time.Second)
  313. select {
  314. case ev := <-sub.C():
  315. t.Fatalf("Errors while pulling: %v", ev)
  316. case <-timeout.C:
  317. t.Fatalf("timed out before index was received")
  318. case <-done:
  319. return
  320. }
  321. fc.mut.Lock()
  322. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  323. expected := map[string]struct{}{ign: {}, ignExisting: {}}
  324. for _, f := range fs {
  325. if _, ok := expected[f.Name]; !ok {
  326. t.Fatalf("Unexpected file %v was updated in index", f.Name)
  327. }
  328. if f.Invalid {
  329. t.Errorf("File %v is still marked as invalid", f.Name)
  330. }
  331. // The unignored files should only have a local version,
  332. // to mark them as in conflict with any other existing versions.
  333. ev := protocol.Vector{}.Update(device1.Short())
  334. if v := f.Version; !v.Equal(ev) {
  335. t.Errorf("File %v has version %v, expected %v", f.Name, v, ev)
  336. }
  337. if f.Name == ign {
  338. if !f.Deleted {
  339. t.Errorf("File %v was not marked as deleted", f.Name)
  340. }
  341. } else if f.Deleted {
  342. t.Errorf("File %v is marked as deleted", f.Name)
  343. }
  344. delete(expected, f.Name)
  345. }
  346. for name := range expected {
  347. t.Errorf("File %v wasn't updated in index", name)
  348. }
  349. done <- struct{}{}
  350. }
  351. // Make sure pulling doesn't interfere, as index updates are racy and
  352. // thus we cannot distinguish between scan and pull results.
  353. fc.requestFn = func(folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
  354. return nil, nil
  355. }
  356. fc.mut.Unlock()
  357. if err := m.SetIgnores("default", []string{"*:ignored*"}); err != nil {
  358. panic(err)
  359. }
  360. timeout = time.NewTimer(5 * time.Second)
  361. select {
  362. case <-timeout.C:
  363. t.Fatalf("timed out before index was received")
  364. case <-done:
  365. return
  366. }
  367. }
  368. func TestIssue4841(t *testing.T) {
  369. m, fc, tmpDir := setupModelWithConnection()
  370. defer m.Stop()
  371. defer os.RemoveAll(tmpDir)
  372. received := make(chan protocol.FileInfo)
  373. fc.mut.Lock()
  374. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  375. if len(fs) != 1 {
  376. t.Fatalf("Sent index with %d files, should be 1", len(fs))
  377. }
  378. if fs[0].Name != "foo" {
  379. t.Fatalf(`Sent index with file %v, should be "foo"`, fs[0].Name)
  380. }
  381. received <- fs[0]
  382. return
  383. }
  384. fc.mut.Unlock()
  385. // Setup file from remote that was ignored locally
  386. m.updateLocals(defaultFolderConfig.ID, []protocol.FileInfo{{
  387. Name: "foo",
  388. Type: protocol.FileInfoTypeFile,
  389. Invalid: true,
  390. Version: protocol.Vector{}.Update(device2.Short()),
  391. }})
  392. <-received
  393. // Scan without ignore patterns with "foo" not existing locally
  394. if err := m.ScanFolder("default"); err != nil {
  395. t.Fatal("Failed scanning:", err)
  396. }
  397. f := <-received
  398. if expected := (protocol.Vector{}.Update(device1.Short())); !f.Version.Equal(expected) {
  399. t.Errorf("Got Version == %v, expected %v", f.Version, expected)
  400. }
  401. }
  402. func TestRescanIfHaveInvalidContent(t *testing.T) {
  403. m, fc, tmpDir := setupModelWithConnection()
  404. defer m.Stop()
  405. defer os.RemoveAll(tmpDir)
  406. payload := []byte("hello")
  407. if err := ioutil.WriteFile(filepath.Join(tmpDir, "foo"), payload, 0777); err != nil {
  408. t.Fatal(err)
  409. }
  410. received := make(chan protocol.FileInfo)
  411. fc.mut.Lock()
  412. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  413. if len(fs) != 1 {
  414. t.Fatalf("Sent index with %d files, should be 1", len(fs))
  415. }
  416. if fs[0].Name != "foo" {
  417. t.Fatalf(`Sent index with file %v, should be "foo"`, fs[0].Name)
  418. }
  419. received <- fs[0]
  420. return
  421. }
  422. fc.mut.Unlock()
  423. // Scan without ignore patterns with "foo" not existing locally
  424. if err := m.ScanFolder("default"); err != nil {
  425. t.Fatal("Failed scanning:", err)
  426. }
  427. f := <-received
  428. if f.Blocks[0].WeakHash != 103547413 {
  429. t.Fatalf("unexpected weak hash: %d != 103547413", f.Blocks[0].WeakHash)
  430. }
  431. buf := make([]byte, len(payload))
  432. err := m.Request(device2, "default", "foo", 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false, buf)
  433. if err != nil {
  434. t.Fatal(err)
  435. }
  436. if !bytes.Equal(buf, payload) {
  437. t.Errorf("%s != %s", buf, payload)
  438. }
  439. payload = []byte("bye")
  440. buf = make([]byte, len(payload))
  441. if err := ioutil.WriteFile(filepath.Join(tmpDir, "foo"), payload, 0777); err != nil {
  442. t.Fatal(err)
  443. }
  444. err = m.Request(device2, "default", "foo", 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false, buf)
  445. if err == nil {
  446. t.Fatalf("expected failure")
  447. }
  448. select {
  449. case f := <-received:
  450. if f.Blocks[0].WeakHash != 41943361 {
  451. t.Fatalf("unexpected weak hash: %d != 41943361", f.Blocks[0].WeakHash)
  452. }
  453. case <-time.After(time.Second):
  454. t.Fatalf("timed out")
  455. }
  456. }
  457. func setupModelWithConnection() (*Model, *fakeConnection, string) {
  458. tmpDir := createTmpDir()
  459. cfg := defaultCfgWrapper.RawCopy()
  460. cfg.Devices = append(cfg.Devices, config.NewDeviceConfiguration(device2, "device2"))
  461. cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpDir)
  462. cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
  463. {DeviceID: device1},
  464. {DeviceID: device2},
  465. }
  466. m, fc := setupModelWithConnectionManual(cfg)
  467. return m, fc, tmpDir
  468. }
  469. func setupModelWithConnectionManual(cfg config.Configuration) (*Model, *fakeConnection) {
  470. w, path := createTmpWrapper(cfg)
  471. defer os.Remove(path)
  472. db := db.OpenMemory()
  473. m := NewModel(w, device1, "syncthing", "dev", db, nil)
  474. m.AddFolder(cfg.Folders[0])
  475. m.ServeBackground()
  476. m.StartFolder("default")
  477. fc := addFakeConn(m, device2)
  478. fc.folder = "default"
  479. m.ScanFolder("default")
  480. return m, fc
  481. }
  482. func createTmpDir() string {
  483. tmpDir, err := ioutil.TempDir("testdata", "_request-")
  484. if err != nil {
  485. panic("Failed to create temporary testing dir")
  486. }
  487. return tmpDir
  488. }
  489. func equalContents(path string, contents []byte) error {
  490. if bs, err := ioutil.ReadFile(path); err != nil {
  491. return err
  492. } else if !bytes.Equal(bs, contents) {
  493. return errors.New("incorrect data")
  494. }
  495. return nil
  496. }