requests_test.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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. "io/ioutil"
  10. "os"
  11. "path/filepath"
  12. "runtime"
  13. "strings"
  14. "testing"
  15. "time"
  16. "github.com/syncthing/syncthing/lib/config"
  17. "github.com/syncthing/syncthing/lib/db"
  18. "github.com/syncthing/syncthing/lib/fs"
  19. "github.com/syncthing/syncthing/lib/protocol"
  20. )
  21. func TestRequestSimple(t *testing.T) {
  22. // Verify that the model performs a request and creates a file based on
  23. // an incoming index update.
  24. m, fc, tmpFolder := setupModelWithConnection()
  25. defer m.Stop()
  26. defer os.RemoveAll(tmpFolder)
  27. // We listen for incoming index updates and trigger when we see one for
  28. // the expected test file.
  29. done := make(chan struct{})
  30. fc.mut.Lock()
  31. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  32. for _, f := range fs {
  33. if f.Name == "testfile" {
  34. close(done)
  35. return
  36. }
  37. }
  38. }
  39. fc.mut.Unlock()
  40. // Send an update for the test file, wait for it to sync and be reported back.
  41. contents := []byte("test file contents\n")
  42. fc.addFile("testfile", 0644, protocol.FileInfoTypeFile, contents)
  43. fc.sendIndexUpdate()
  44. <-done
  45. // Verify the contents
  46. bs, err := ioutil.ReadFile(filepath.Join(tmpFolder, "testfile"))
  47. if err != nil {
  48. t.Error("File did not sync correctly:", err)
  49. return
  50. }
  51. if !bytes.Equal(bs, contents) {
  52. t.Error("File did not sync correctly: incorrect data")
  53. }
  54. }
  55. func TestSymlinkTraversalRead(t *testing.T) {
  56. // Verify that a symlink can not be traversed for reading.
  57. if runtime.GOOS == "windows" {
  58. t.Skip("no symlink support on CI")
  59. return
  60. }
  61. m, fc, tmpFolder := setupModelWithConnection()
  62. defer m.Stop()
  63. defer os.RemoveAll(tmpFolder)
  64. // We listen for incoming index updates and trigger when we see one for
  65. // the expected test file.
  66. done := make(chan struct{})
  67. fc.mut.Lock()
  68. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  69. for _, f := range fs {
  70. if f.Name == "symlink" {
  71. close(done)
  72. return
  73. }
  74. }
  75. }
  76. fc.mut.Unlock()
  77. // Send an update for the symlink, wait for it to sync and be reported back.
  78. contents := []byte("..")
  79. fc.addFile("symlink", 0644, protocol.FileInfoTypeSymlink, contents)
  80. fc.sendIndexUpdate()
  81. <-done
  82. // Request a file by traversing the symlink
  83. buf := make([]byte, 10)
  84. err := m.Request(device1, "default", "symlink/requests_test.go", 0, nil, false, buf)
  85. if err == nil || !bytes.Equal(buf, make([]byte, 10)) {
  86. t.Error("Managed to traverse symlink")
  87. }
  88. }
  89. func TestSymlinkTraversalWrite(t *testing.T) {
  90. // Verify that a symlink can not be traversed for writing.
  91. if runtime.GOOS == "windows" {
  92. t.Skip("no symlink support on CI")
  93. return
  94. }
  95. m, fc, tmpFolder := setupModelWithConnection()
  96. defer m.Stop()
  97. defer os.RemoveAll(tmpFolder)
  98. // We listen for incoming index updates and trigger when we see one for
  99. // the expected names.
  100. done := make(chan struct{}, 1)
  101. badReq := make(chan string, 1)
  102. badIdx := make(chan string, 1)
  103. fc.mut.Lock()
  104. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  105. for _, f := range fs {
  106. if f.Name == "symlink" {
  107. done <- struct{}{}
  108. return
  109. }
  110. if strings.HasPrefix(f.Name, "symlink") {
  111. badIdx <- f.Name
  112. return
  113. }
  114. }
  115. }
  116. fc.requestFn = func(folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
  117. if name != "symlink" && strings.HasPrefix(name, "symlink") {
  118. badReq <- name
  119. }
  120. return fc.fileData[name], nil
  121. }
  122. fc.mut.Unlock()
  123. // Send an update for the symlink, wait for it to sync and be reported back.
  124. contents := []byte("..")
  125. fc.addFile("symlink", 0644, protocol.FileInfoTypeSymlink, contents)
  126. fc.sendIndexUpdate()
  127. <-done
  128. // Send an update for things behind the symlink, wait for requests for
  129. // blocks for any of them to come back, or index entries. Hopefully none
  130. // of that should happen.
  131. contents = []byte("testdata testdata\n")
  132. fc.addFile("symlink/testfile", 0644, protocol.FileInfoTypeFile, contents)
  133. fc.addFile("symlink/testdir", 0644, protocol.FileInfoTypeDirectory, contents)
  134. fc.addFile("symlink/testsyml", 0644, protocol.FileInfoTypeSymlink, contents)
  135. fc.sendIndexUpdate()
  136. select {
  137. case name := <-badReq:
  138. t.Fatal("Should not have requested the data for", name)
  139. case name := <-badIdx:
  140. t.Fatal("Should not have sent the index entry for", name)
  141. case <-time.After(3 * time.Second):
  142. // Unfortunately not much else to trigger on here. The puller sleep
  143. // interval is 1s so if we didn't get any requests within two
  144. // iterations we should be fine.
  145. }
  146. }
  147. func TestRequestCreateTmpSymlink(t *testing.T) {
  148. // Test that an update for a temporary file is invalidated
  149. m, fc, tmpFolder := setupModelWithConnection()
  150. defer m.Stop()
  151. defer os.RemoveAll(tmpFolder)
  152. // We listen for incoming index updates and trigger when we see one for
  153. // the expected test file.
  154. goodIdx := make(chan struct{})
  155. name := fs.TempName("testlink")
  156. fc.mut.Lock()
  157. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  158. for _, f := range fs {
  159. if f.Name == name {
  160. if f.Invalid {
  161. goodIdx <- struct{}{}
  162. } else {
  163. t.Fatal("Received index with non-invalid temporary file")
  164. }
  165. return
  166. }
  167. }
  168. }
  169. fc.mut.Unlock()
  170. // Send an update for the test file, wait for it to sync and be reported back.
  171. fc.addFile(name, 0644, protocol.FileInfoTypeSymlink, []byte(".."))
  172. fc.sendIndexUpdate()
  173. select {
  174. case <-goodIdx:
  175. case <-time.After(3 * time.Second):
  176. t.Fatal("Timed out without index entry being sent")
  177. }
  178. }
  179. func TestRequestVersioningSymlinkAttack(t *testing.T) {
  180. if runtime.GOOS == "windows" {
  181. t.Skip("no symlink support on Windows")
  182. }
  183. // Sets up a folder with trashcan versioning and tries to use a
  184. // deleted symlink to escape
  185. tmpFolder, err := ioutil.TempDir(".", "_request-")
  186. if err != nil {
  187. panic("Failed to create temporary testing dir")
  188. }
  189. cfg := defaultConfig.RawCopy()
  190. cfg.Folders[0] = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, tmpFolder)
  191. cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
  192. {DeviceID: device1},
  193. {DeviceID: device2},
  194. }
  195. cfg.Folders[0].Versioning = config.VersioningConfiguration{
  196. Type: "trashcan",
  197. }
  198. w := config.Wrap("/tmp/cfg", cfg)
  199. db := db.OpenMemory()
  200. m := NewModel(w, device1, "syncthing", "dev", db, nil)
  201. m.AddFolder(cfg.Folders[0])
  202. m.ServeBackground()
  203. m.StartFolder("default")
  204. defer m.Stop()
  205. defer os.RemoveAll(tmpFolder)
  206. fc := addFakeConn(m, device2)
  207. fc.folder = "default"
  208. // Create a temporary directory that we will use as target to see if
  209. // we can escape to it
  210. tmpdir, err := ioutil.TempDir("", "syncthing-test")
  211. if err != nil {
  212. t.Fatal(err)
  213. }
  214. // We listen for incoming index updates and trigger when we see one for
  215. // the expected test file.
  216. idx := make(chan int)
  217. fc.mut.Lock()
  218. fc.indexFn = func(folder string, fs []protocol.FileInfo) {
  219. idx <- len(fs)
  220. }
  221. fc.mut.Unlock()
  222. // Send an update for the test file, wait for it to sync and be reported back.
  223. fc.addFile("foo", 0644, protocol.FileInfoTypeSymlink, []byte(tmpdir))
  224. fc.sendIndexUpdate()
  225. for updates := 0; updates < 1; updates += <-idx {
  226. }
  227. // Delete the symlink, hoping for it to get versioned
  228. fc.deleteFile("foo")
  229. fc.sendIndexUpdate()
  230. for updates := 0; updates < 1; updates += <-idx {
  231. }
  232. // Recreate foo and a file in it with some data
  233. fc.addFile("foo", 0755, protocol.FileInfoTypeDirectory, nil)
  234. fc.addFile("foo/test", 0644, protocol.FileInfoTypeFile, []byte("testtesttest"))
  235. fc.sendIndexUpdate()
  236. for updates := 0; updates < 1; updates += <-idx {
  237. }
  238. // Remove the test file and see if it escaped
  239. fc.deleteFile("foo/test")
  240. fc.sendIndexUpdate()
  241. for updates := 0; updates < 1; updates += <-idx {
  242. }
  243. path := filepath.Join(tmpdir, "test")
  244. if _, err := os.Lstat(path); !os.IsNotExist(err) {
  245. t.Fatal("File escaped to", path)
  246. }
  247. }
  248. func setupModelWithConnection() (*Model, *fakeConnection, string) {
  249. tmpFolder, err := ioutil.TempDir(".", "_request-")
  250. if err != nil {
  251. panic("Failed to create temporary testing dir")
  252. }
  253. cfg := defaultConfig.RawCopy()
  254. cfg.Folders[0] = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, tmpFolder)
  255. cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
  256. {DeviceID: device1},
  257. {DeviceID: device2},
  258. }
  259. w := config.Wrap("/tmp/cfg", cfg)
  260. db := db.OpenMemory()
  261. m := NewModel(w, device1, "syncthing", "dev", db, nil)
  262. m.AddFolder(cfg.Folders[0])
  263. m.ServeBackground()
  264. m.StartFolder("default")
  265. fc := addFakeConn(m, device2)
  266. fc.folder = "default"
  267. return m, fc, tmpFolder
  268. }