requests_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  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, 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, err := ioutil.TempDir(".", "_request-")
  184. if err != nil {
  185. panic("Failed to create temporary testing dir")
  186. }
  187. cfg := defaultConfig.RawCopy()
  188. cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpDir)
  189. cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
  190. {DeviceID: device1},
  191. {DeviceID: device2},
  192. }
  193. cfg.Folders[0].Versioning = config.VersioningConfiguration{
  194. Type: "trashcan",
  195. }
  196. w := config.Wrap("/tmp/cfg", cfg)
  197. db := db.OpenMemory()
  198. m := NewModel(w, device1, "syncthing", "dev", db, nil)
  199. m.AddFolder(cfg.Folders[0])
  200. m.ServeBackground()
  201. m.StartFolder("default")
  202. defer m.Stop()
  203. defer os.RemoveAll(tmpDir)
  204. fc := addFakeConn(m, device2)
  205. fc.folder = "default"
  206. // Create a temporary directory that we will use as target to see if
  207. // we can escape to it
  208. tmpdir, err := ioutil.TempDir("", "syncthing-test")
  209. if err != nil {
  210. t.Fatal(err)
  211. }
  212. // We listen for incoming index updates and trigger when we see one for
  213. // the expected test file.
  214. idx := make(chan int)
  215. fc.mut.Lock()
  216. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  217. idx <- len(fs)
  218. }
  219. fc.mut.Unlock()
  220. // Send an update for the test file, wait for it to sync and be reported back.
  221. fc.addFile("foo", 0644, protocol.FileInfoTypeSymlink, []byte(tmpdir))
  222. fc.sendIndexUpdate()
  223. for updates := 0; updates < 1; updates += <-idx {
  224. }
  225. // Delete the symlink, hoping for it to get versioned
  226. fc.deleteFile("foo")
  227. fc.sendIndexUpdate()
  228. for updates := 0; updates < 1; updates += <-idx {
  229. }
  230. // Recreate foo and a file in it with some data
  231. fc.addFile("foo", 0755, protocol.FileInfoTypeDirectory, nil)
  232. fc.addFile("foo/test", 0644, protocol.FileInfoTypeFile, []byte("testtesttest"))
  233. fc.sendIndexUpdate()
  234. for updates := 0; updates < 1; updates += <-idx {
  235. }
  236. // Remove the test file and see if it escaped
  237. fc.deleteFile("foo/test")
  238. fc.sendIndexUpdate()
  239. for updates := 0; updates < 1; updates += <-idx {
  240. }
  241. path := filepath.Join(tmpdir, "test")
  242. if _, err := os.Lstat(path); !os.IsNotExist(err) {
  243. t.Fatal("File escaped to", path)
  244. }
  245. }
  246. func TestPullInvalidIgnoredSO(t *testing.T) {
  247. pullInvalidIgnored(t, config.FolderTypeSendOnly)
  248. }
  249. func TestPullInvalidIgnoredSR(t *testing.T) {
  250. pullInvalidIgnored(t, config.FolderTypeSendReceive)
  251. }
  252. // This test checks that (un-)ignored/invalid/deleted files are treated as expected.
  253. func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
  254. t.Helper()
  255. tmpDir := createTmpDir()
  256. defer os.RemoveAll(tmpDir)
  257. cfg := defaultConfig.RawCopy()
  258. cfg.Devices = append(cfg.Devices, config.NewDeviceConfiguration(device2, "device2"))
  259. cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpDir)
  260. cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
  261. {DeviceID: device1},
  262. {DeviceID: device2},
  263. }
  264. cfg.Folders[0].Type = ft
  265. m, fc := setupModelWithConnectionManual(cfg)
  266. defer m.Stop()
  267. // Reach in and update the ignore matcher to one that always does
  268. // reloads when asked to, instead of checking file mtimes. This is
  269. // because we might be changing the files on disk often enough that the
  270. // mtimes will be unreliable to determine change status.
  271. m.fmut.Lock()
  272. m.folderIgnores["default"] = ignore.New(cfg.Folders[0].Filesystem(), ignore.WithChangeDetector(newAlwaysChanged()))
  273. m.fmut.Unlock()
  274. if err := m.SetIgnores("default", []string{"*ignored*"}); err != nil {
  275. panic(err)
  276. }
  277. contents := []byte("test file contents\n")
  278. otherContents := []byte("other test file contents\n")
  279. invIgn := "invalid:ignored"
  280. invDel := "invalid:deleted"
  281. ign := "ignoredNonExisting"
  282. ignExisting := "ignoredExisting"
  283. fc.addFile(invIgn, 0644, protocol.FileInfoTypeFile, contents)
  284. fc.addFile(invDel, 0644, protocol.FileInfoTypeFile, contents)
  285. fc.deleteFile(invDel)
  286. fc.addFile(ign, 0644, protocol.FileInfoTypeFile, contents)
  287. fc.addFile(ignExisting, 0644, protocol.FileInfoTypeFile, contents)
  288. if err := ioutil.WriteFile(filepath.Join(tmpDir, ignExisting), otherContents, 0644); err != nil {
  289. panic(err)
  290. }
  291. done := make(chan struct{})
  292. fc.mut.Lock()
  293. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  294. expected := map[string]struct{}{invIgn: {}, ign: {}, ignExisting: {}}
  295. for _, f := range fs {
  296. if _, ok := expected[f.Name]; !ok {
  297. t.Fatalf("Unexpected file %v was added to index", f.Name)
  298. }
  299. if !f.Invalid {
  300. t.Errorf("File %v wasn't marked as invalid", f.Name)
  301. }
  302. delete(expected, f.Name)
  303. }
  304. for name := range expected {
  305. t.Errorf("File %v wasn't added to index", name)
  306. }
  307. done <- struct{}{}
  308. }
  309. fc.mut.Unlock()
  310. sub := events.Default.Subscribe(events.FolderErrors)
  311. defer events.Default.Unsubscribe(sub)
  312. fc.sendIndexUpdate()
  313. timeout := time.NewTimer(5 * time.Second)
  314. select {
  315. case ev := <-sub.C():
  316. t.Fatalf("Errors while pulling: %v", ev)
  317. case <-timeout.C:
  318. t.Fatalf("timed out before index was received")
  319. case <-done:
  320. return
  321. }
  322. fc.mut.Lock()
  323. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  324. expected := map[string]struct{}{ign: {}, ignExisting: {}}
  325. for _, f := range fs {
  326. if _, ok := expected[f.Name]; !ok {
  327. t.Fatalf("Unexpected file %v was updated in index", f.Name)
  328. }
  329. if f.Invalid {
  330. t.Errorf("File %v is still marked as invalid", f.Name)
  331. }
  332. // The unignored files should only have a local version,
  333. // to mark them as in conflict with any other existing versions.
  334. ev := protocol.Vector{}.Update(device1.Short())
  335. if v := f.Version; !v.Equal(ev) {
  336. t.Errorf("File %v has version %v, expected %v", f.Name, v, ev)
  337. }
  338. if f.Name == ign {
  339. if !f.Deleted {
  340. t.Errorf("File %v was not marked as deleted", f.Name)
  341. }
  342. } else if f.Deleted {
  343. t.Errorf("File %v is marked as deleted", f.Name)
  344. }
  345. delete(expected, f.Name)
  346. }
  347. for name := range expected {
  348. t.Errorf("File %v wasn't updated in index", name)
  349. }
  350. done <- struct{}{}
  351. }
  352. // Make sure pulling doesn't interfere, as index updates are racy and
  353. // thus we cannot distinguish between scan and pull results.
  354. fc.requestFn = func(folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
  355. return nil, nil
  356. }
  357. fc.mut.Unlock()
  358. if err := m.SetIgnores("default", []string{"*:ignored*"}); err != nil {
  359. panic(err)
  360. }
  361. timeout = time.NewTimer(5 * time.Second)
  362. select {
  363. case <-timeout.C:
  364. t.Fatalf("timed out before index was received")
  365. case <-done:
  366. return
  367. }
  368. }
  369. func setupModelWithConnection() (*Model, *fakeConnection, string) {
  370. tmpDir := createTmpDir()
  371. cfg := defaultConfig.RawCopy()
  372. cfg.Devices = append(cfg.Devices, config.NewDeviceConfiguration(device2, "device2"))
  373. cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpDir)
  374. cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
  375. {DeviceID: device1},
  376. {DeviceID: device2},
  377. }
  378. m, fc := setupModelWithConnectionManual(cfg)
  379. return m, fc, tmpDir
  380. }
  381. func setupModelWithConnectionManual(cfg config.Configuration) (*Model, *fakeConnection) {
  382. w := config.Wrap("/tmp/cfg", cfg)
  383. db := db.OpenMemory()
  384. m := NewModel(w, device1, "syncthing", "dev", db, nil)
  385. m.AddFolder(cfg.Folders[0])
  386. m.ServeBackground()
  387. m.StartFolder("default")
  388. fc := addFakeConn(m, device2)
  389. fc.folder = "default"
  390. m.ScanFolder("default")
  391. return m, fc
  392. }
  393. func createTmpDir() string {
  394. tmpDir, err := ioutil.TempDir(".", "_request-")
  395. if err != nil {
  396. panic("Failed to create temporary testing dir")
  397. }
  398. return tmpDir
  399. }
  400. func equalContents(path string, contents []byte) error {
  401. if bs, err := ioutil.ReadFile(path); err != nil {
  402. return err
  403. } else if !bytes.Equal(bs, contents) {
  404. return errors.New("incorrect data")
  405. }
  406. return nil
  407. }