requests_test.go 8.2 KB

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