archive_test.go 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482
  1. package archive // import "github.com/docker/docker/pkg/archive"
  2. import (
  3. "archive/tar"
  4. "bytes"
  5. "compress/gzip"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "io/fs"
  10. "os"
  11. "os/exec"
  12. "path/filepath"
  13. "reflect"
  14. "runtime"
  15. "strings"
  16. "testing"
  17. "time"
  18. "github.com/containerd/containerd/pkg/userns"
  19. "github.com/docker/docker/pkg/idtools"
  20. "github.com/docker/docker/pkg/ioutils"
  21. "gotest.tools/v3/assert"
  22. is "gotest.tools/v3/assert/cmp"
  23. "gotest.tools/v3/skip"
  24. )
  25. var tmp string
  26. func init() {
  27. tmp = "/tmp/"
  28. if runtime.GOOS == "windows" {
  29. tmp = os.Getenv("TEMP") + `\`
  30. }
  31. }
  32. var defaultArchiver = NewDefaultArchiver()
  33. func defaultTarUntar(src, dst string) error {
  34. return defaultArchiver.TarUntar(src, dst)
  35. }
  36. func defaultUntarPath(src, dst string) error {
  37. return defaultArchiver.UntarPath(src, dst)
  38. }
  39. func defaultCopyFileWithTar(src, dst string) (err error) {
  40. return defaultArchiver.CopyFileWithTar(src, dst)
  41. }
  42. func defaultCopyWithTar(src, dst string) error {
  43. return defaultArchiver.CopyWithTar(src, dst)
  44. }
  45. func TestIsArchivePathDir(t *testing.T) {
  46. cmd := exec.Command("sh", "-c", "mkdir -p /tmp/archivedir")
  47. output, err := cmd.CombinedOutput()
  48. if err != nil {
  49. t.Fatalf("Fail to create an archive file for test : %s.", output)
  50. }
  51. if IsArchivePath(tmp + "archivedir") {
  52. t.Fatalf("Incorrectly recognised directory as an archive")
  53. }
  54. }
  55. func TestIsArchivePathInvalidFile(t *testing.T) {
  56. cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1024 count=1 of=/tmp/archive && gzip --stdout /tmp/archive > /tmp/archive.gz")
  57. output, err := cmd.CombinedOutput()
  58. if err != nil {
  59. t.Fatalf("Fail to create an archive file for test : %s.", output)
  60. }
  61. if IsArchivePath(tmp + "archive") {
  62. t.Fatalf("Incorrectly recognised invalid tar path as archive")
  63. }
  64. if IsArchivePath(tmp + "archive.gz") {
  65. t.Fatalf("Incorrectly recognised invalid compressed tar path as archive")
  66. }
  67. }
  68. func TestIsArchivePathTar(t *testing.T) {
  69. whichTar := "tar"
  70. cmdStr := fmt.Sprintf("touch /tmp/archivedata && %s -cf /tmp/archive /tmp/archivedata && gzip --stdout /tmp/archive > /tmp/archive.gz", whichTar)
  71. cmd := exec.Command("sh", "-c", cmdStr)
  72. output, err := cmd.CombinedOutput()
  73. if err != nil {
  74. t.Fatalf("Fail to create an archive file for test : %s.", output)
  75. }
  76. if !IsArchivePath(tmp + "/archive") {
  77. t.Fatalf("Did not recognise valid tar path as archive")
  78. }
  79. if !IsArchivePath(tmp + "archive.gz") {
  80. t.Fatalf("Did not recognise valid compressed tar path as archive")
  81. }
  82. }
  83. func testDecompressStream(t *testing.T, ext, compressCommand string) io.Reader {
  84. cmd := exec.Command("sh", "-c",
  85. fmt.Sprintf("touch /tmp/archive && %s /tmp/archive", compressCommand))
  86. output, err := cmd.CombinedOutput()
  87. if err != nil {
  88. t.Fatalf("Failed to create an archive file for test : %s.", output)
  89. }
  90. filename := "archive." + ext
  91. archive, err := os.Open(tmp + filename)
  92. if err != nil {
  93. t.Fatalf("Failed to open file %s: %v", filename, err)
  94. }
  95. defer archive.Close()
  96. r, err := DecompressStream(archive)
  97. if err != nil {
  98. t.Fatalf("Failed to decompress %s: %v", filename, err)
  99. }
  100. if _, err = io.ReadAll(r); err != nil {
  101. t.Fatalf("Failed to read the decompressed stream: %v ", err)
  102. }
  103. if err = r.Close(); err != nil {
  104. t.Fatalf("Failed to close the decompressed stream: %v ", err)
  105. }
  106. return r
  107. }
  108. func TestDecompressStreamGzip(t *testing.T) {
  109. testDecompressStream(t, "gz", "gzip -f")
  110. }
  111. func TestDecompressStreamBzip2(t *testing.T) {
  112. testDecompressStream(t, "bz2", "bzip2 -f")
  113. }
  114. func TestDecompressStreamXz(t *testing.T) {
  115. if runtime.GOOS == "windows" {
  116. t.Skip("Xz not present in msys2")
  117. }
  118. testDecompressStream(t, "xz", "xz -f")
  119. }
  120. func TestDecompressStreamZstd(t *testing.T) {
  121. if _, err := exec.LookPath("zstd"); err != nil {
  122. t.Skip("zstd not installed")
  123. }
  124. testDecompressStream(t, "zst", "zstd -f")
  125. }
  126. func TestCompressStreamXzUnsupported(t *testing.T) {
  127. dest, err := os.Create(tmp + "dest")
  128. if err != nil {
  129. t.Fatalf("Fail to create the destination file")
  130. }
  131. defer dest.Close()
  132. _, err = CompressStream(dest, Xz)
  133. if err == nil {
  134. t.Fatalf("Should fail as xz is unsupported for compression format.")
  135. }
  136. }
  137. func TestCompressStreamBzip2Unsupported(t *testing.T) {
  138. dest, err := os.Create(tmp + "dest")
  139. if err != nil {
  140. t.Fatalf("Fail to create the destination file")
  141. }
  142. defer dest.Close()
  143. _, err = CompressStream(dest, Bzip2)
  144. if err == nil {
  145. t.Fatalf("Should fail as bzip2 is unsupported for compression format.")
  146. }
  147. }
  148. func TestCompressStreamInvalid(t *testing.T) {
  149. dest, err := os.Create(tmp + "dest")
  150. if err != nil {
  151. t.Fatalf("Fail to create the destination file")
  152. }
  153. defer dest.Close()
  154. _, err = CompressStream(dest, -1)
  155. if err == nil {
  156. t.Fatalf("Should fail as xz is unsupported for compression format.")
  157. }
  158. }
  159. func TestExtensionInvalid(t *testing.T) {
  160. compression := Compression(-1)
  161. output := compression.Extension()
  162. if output != "" {
  163. t.Fatalf("The extension of an invalid compression should be an empty string.")
  164. }
  165. }
  166. func TestExtensionUncompressed(t *testing.T) {
  167. compression := Uncompressed
  168. output := compression.Extension()
  169. if output != "tar" {
  170. t.Fatalf("The extension of an uncompressed archive should be 'tar'.")
  171. }
  172. }
  173. func TestExtensionBzip2(t *testing.T) {
  174. compression := Bzip2
  175. output := compression.Extension()
  176. if output != "tar.bz2" {
  177. t.Fatalf("The extension of a bzip2 archive should be 'tar.bz2'")
  178. }
  179. }
  180. func TestExtensionGzip(t *testing.T) {
  181. compression := Gzip
  182. output := compression.Extension()
  183. if output != "tar.gz" {
  184. t.Fatalf("The extension of a gzip archive should be 'tar.gz'")
  185. }
  186. }
  187. func TestExtensionXz(t *testing.T) {
  188. compression := Xz
  189. output := compression.Extension()
  190. if output != "tar.xz" {
  191. t.Fatalf("The extension of a xz archive should be 'tar.xz'")
  192. }
  193. }
  194. func TestExtensionZstd(t *testing.T) {
  195. compression := Zstd
  196. output := compression.Extension()
  197. if output != "tar.zst" {
  198. t.Fatalf("The extension of a zstd archive should be 'tar.zst'")
  199. }
  200. }
  201. func TestCmdStreamLargeStderr(t *testing.T) {
  202. cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
  203. out, err := cmdStream(cmd, nil)
  204. if err != nil {
  205. t.Fatalf("Failed to start command: %s", err)
  206. }
  207. errCh := make(chan error, 1)
  208. go func() {
  209. _, err := io.Copy(io.Discard, out)
  210. errCh <- err
  211. }()
  212. select {
  213. case err := <-errCh:
  214. if err != nil {
  215. t.Fatalf("Command should not have failed (err=%.100s...)", err)
  216. }
  217. case <-time.After(5 * time.Second):
  218. t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
  219. }
  220. }
  221. func TestCmdStreamBad(t *testing.T) {
  222. // TODO Windows: Figure out why this is failing in CI but not locally
  223. if runtime.GOOS == "windows" {
  224. t.Skip("Failing on Windows CI machines")
  225. }
  226. badCmd := exec.Command("sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
  227. out, err := cmdStream(badCmd, nil)
  228. if err != nil {
  229. t.Fatalf("Failed to start command: %s", err)
  230. }
  231. if output, err := io.ReadAll(out); err == nil {
  232. t.Fatalf("Command should have failed")
  233. } else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
  234. t.Fatalf("Wrong error value (%s)", err)
  235. } else if s := string(output); s != "hello\n" {
  236. t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
  237. }
  238. }
  239. func TestCmdStreamGood(t *testing.T) {
  240. cmd := exec.Command("sh", "-c", "echo hello; exit 0")
  241. out, err := cmdStream(cmd, nil)
  242. if err != nil {
  243. t.Fatal(err)
  244. }
  245. if output, err := io.ReadAll(out); err != nil {
  246. t.Fatalf("Command should not have failed (err=%s)", err)
  247. } else if s := string(output); s != "hello\n" {
  248. t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
  249. }
  250. }
  251. func TestUntarPathWithInvalidDest(t *testing.T) {
  252. tempFolder, err := os.MkdirTemp("", "docker-archive-test")
  253. assert.NilError(t, err)
  254. defer os.RemoveAll(tempFolder)
  255. invalidDestFolder := filepath.Join(tempFolder, "invalidDest")
  256. // Create a src file
  257. srcFile := filepath.Join(tempFolder, "src")
  258. tarFile := filepath.Join(tempFolder, "src.tar")
  259. f, err := os.Create(srcFile)
  260. if assert.Check(t, err) {
  261. _ = f.Close()
  262. }
  263. d, err := os.Create(invalidDestFolder) // being a file (not dir) should cause an error
  264. if assert.Check(t, err) {
  265. _ = d.Close()
  266. }
  267. // Translate back to Unix semantics as next exec.Command is run under sh
  268. srcFileU := srcFile
  269. tarFileU := tarFile
  270. if runtime.GOOS == "windows" {
  271. tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
  272. srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
  273. }
  274. cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
  275. _, err = cmd.CombinedOutput()
  276. assert.NilError(t, err)
  277. err = defaultUntarPath(tarFile, invalidDestFolder)
  278. if err == nil {
  279. t.Fatalf("UntarPath with invalid destination path should throw an error.")
  280. }
  281. }
  282. func TestUntarPathWithInvalidSrc(t *testing.T) {
  283. dest, err := os.MkdirTemp("", "docker-archive-test")
  284. if err != nil {
  285. t.Fatalf("Fail to create the destination file")
  286. }
  287. defer os.RemoveAll(dest)
  288. err = defaultUntarPath("/invalid/path", dest)
  289. if err == nil {
  290. t.Fatalf("UntarPath with invalid src path should throw an error.")
  291. }
  292. }
  293. func TestUntarPath(t *testing.T) {
  294. skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
  295. tmpFolder, err := os.MkdirTemp("", "docker-archive-test")
  296. assert.NilError(t, err)
  297. defer os.RemoveAll(tmpFolder)
  298. srcFile := filepath.Join(tmpFolder, "src")
  299. tarFile := filepath.Join(tmpFolder, "src.tar")
  300. f, err := os.Create(filepath.Join(tmpFolder, "src"))
  301. if assert.Check(t, err) {
  302. _ = f.Close()
  303. }
  304. destFolder := filepath.Join(tmpFolder, "dest")
  305. err = os.MkdirAll(destFolder, 0o740)
  306. if err != nil {
  307. t.Fatalf("Fail to create the destination file")
  308. }
  309. // Translate back to Unix semantics as next exec.Command is run under sh
  310. srcFileU := srcFile
  311. tarFileU := tarFile
  312. if runtime.GOOS == "windows" {
  313. tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
  314. srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
  315. }
  316. cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
  317. _, err = cmd.CombinedOutput()
  318. assert.NilError(t, err)
  319. err = defaultUntarPath(tarFile, destFolder)
  320. if err != nil {
  321. t.Fatalf("UntarPath shouldn't throw an error, %s.", err)
  322. }
  323. expectedFile := filepath.Join(destFolder, srcFileU)
  324. _, err = os.Stat(expectedFile)
  325. if err != nil {
  326. t.Fatalf("Destination folder should contain the source file but did not.")
  327. }
  328. }
  329. // Do the same test as above but with the destination as file, it should fail
  330. func TestUntarPathWithDestinationFile(t *testing.T) {
  331. tmpFolder, err := os.MkdirTemp("", "docker-archive-test")
  332. if err != nil {
  333. t.Fatal(err)
  334. }
  335. defer os.RemoveAll(tmpFolder)
  336. srcFile := filepath.Join(tmpFolder, "src")
  337. tarFile := filepath.Join(tmpFolder, "src.tar")
  338. f, err := os.Create(filepath.Join(tmpFolder, "src"))
  339. if assert.Check(t, err) {
  340. _ = f.Close()
  341. }
  342. // Translate back to Unix semantics as next exec.Command is run under sh
  343. srcFileU := srcFile
  344. tarFileU := tarFile
  345. if runtime.GOOS == "windows" {
  346. tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
  347. srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
  348. }
  349. cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
  350. _, err = cmd.CombinedOutput()
  351. if err != nil {
  352. t.Fatal(err)
  353. }
  354. destFile := filepath.Join(tmpFolder, "dest")
  355. f, err = os.Create(destFile)
  356. if assert.Check(t, err) {
  357. _ = f.Close()
  358. }
  359. err = defaultUntarPath(tarFile, destFile)
  360. if err == nil {
  361. t.Fatalf("UntarPath should throw an error if the destination if a file")
  362. }
  363. }
  364. // Do the same test as above but with the destination folder already exists
  365. // and the destination file is a directory
  366. // It's working, see https://github.com/docker/docker/issues/10040
  367. func TestUntarPathWithDestinationSrcFileAsFolder(t *testing.T) {
  368. tmpFolder, err := os.MkdirTemp("", "docker-archive-test")
  369. if err != nil {
  370. t.Fatal(err)
  371. }
  372. defer os.RemoveAll(tmpFolder)
  373. srcFile := filepath.Join(tmpFolder, "src")
  374. tarFile := filepath.Join(tmpFolder, "src.tar")
  375. f, err := os.Create(srcFile)
  376. if assert.Check(t, err) {
  377. _ = f.Close()
  378. }
  379. // Translate back to Unix semantics as next exec.Command is run under sh
  380. srcFileU := srcFile
  381. tarFileU := tarFile
  382. if runtime.GOOS == "windows" {
  383. tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
  384. srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
  385. }
  386. cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
  387. _, err = cmd.CombinedOutput()
  388. if err != nil {
  389. t.Fatal(err)
  390. }
  391. destFolder := filepath.Join(tmpFolder, "dest")
  392. err = os.MkdirAll(destFolder, 0o740)
  393. if err != nil {
  394. t.Fatalf("Fail to create the destination folder")
  395. }
  396. // Let's create a folder that will has the same path as the extracted file (from tar)
  397. destSrcFileAsFolder := filepath.Join(destFolder, srcFileU)
  398. err = os.MkdirAll(destSrcFileAsFolder, 0o740)
  399. if err != nil {
  400. t.Fatal(err)
  401. }
  402. err = defaultUntarPath(tarFile, destFolder)
  403. if err != nil {
  404. t.Fatalf("UntarPath should throw not throw an error if the extracted file already exists and is a folder")
  405. }
  406. }
  407. func TestCopyWithTarInvalidSrc(t *testing.T) {
  408. tempFolder, err := os.MkdirTemp("", "docker-archive-test")
  409. if err != nil {
  410. t.Fatal(nil)
  411. }
  412. destFolder := filepath.Join(tempFolder, "dest")
  413. invalidSrc := filepath.Join(tempFolder, "doesnotexists")
  414. err = os.MkdirAll(destFolder, 0o740)
  415. if err != nil {
  416. t.Fatal(err)
  417. }
  418. err = defaultCopyWithTar(invalidSrc, destFolder)
  419. if err == nil {
  420. t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
  421. }
  422. }
  423. func TestCopyWithTarInexistentDestWillCreateIt(t *testing.T) {
  424. skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
  425. tempFolder, err := os.MkdirTemp("", "docker-archive-test")
  426. if err != nil {
  427. t.Fatal(nil)
  428. }
  429. srcFolder := filepath.Join(tempFolder, "src")
  430. inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists")
  431. err = os.MkdirAll(srcFolder, 0o740)
  432. if err != nil {
  433. t.Fatal(err)
  434. }
  435. err = defaultCopyWithTar(srcFolder, inexistentDestFolder)
  436. if err != nil {
  437. t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
  438. }
  439. _, err = os.Stat(inexistentDestFolder)
  440. if err != nil {
  441. t.Fatalf("CopyWithTar with an inexistent folder should create it.")
  442. }
  443. }
  444. // Test CopyWithTar with a file as src
  445. func TestCopyWithTarSrcFile(t *testing.T) {
  446. folder, err := os.MkdirTemp("", "docker-archive-test")
  447. if err != nil {
  448. t.Fatal(err)
  449. }
  450. defer os.RemoveAll(folder)
  451. dest := filepath.Join(folder, "dest")
  452. srcFolder := filepath.Join(folder, "src")
  453. src := filepath.Join(folder, filepath.Join("src", "src"))
  454. err = os.MkdirAll(srcFolder, 0o740)
  455. if err != nil {
  456. t.Fatal(err)
  457. }
  458. err = os.MkdirAll(dest, 0o740)
  459. if err != nil {
  460. t.Fatal(err)
  461. }
  462. os.WriteFile(src, []byte("content"), 0o777)
  463. err = defaultCopyWithTar(src, dest)
  464. if err != nil {
  465. t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
  466. }
  467. _, err = os.Stat(dest)
  468. // FIXME Check the content
  469. if err != nil {
  470. t.Fatalf("Destination file should be the same as the source.")
  471. }
  472. }
  473. // Test CopyWithTar with a folder as src
  474. func TestCopyWithTarSrcFolder(t *testing.T) {
  475. folder, err := os.MkdirTemp("", "docker-archive-test")
  476. if err != nil {
  477. t.Fatal(err)
  478. }
  479. defer os.RemoveAll(folder)
  480. dest := filepath.Join(folder, "dest")
  481. src := filepath.Join(folder, filepath.Join("src", "folder"))
  482. err = os.MkdirAll(src, 0o740)
  483. if err != nil {
  484. t.Fatal(err)
  485. }
  486. err = os.MkdirAll(dest, 0o740)
  487. if err != nil {
  488. t.Fatal(err)
  489. }
  490. os.WriteFile(filepath.Join(src, "file"), []byte("content"), 0o777)
  491. err = defaultCopyWithTar(src, dest)
  492. if err != nil {
  493. t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
  494. }
  495. _, err = os.Stat(dest)
  496. // FIXME Check the content (the file inside)
  497. if err != nil {
  498. t.Fatalf("Destination folder should contain the source file but did not.")
  499. }
  500. }
  501. func TestCopyFileWithTarInvalidSrc(t *testing.T) {
  502. tempFolder, err := os.MkdirTemp("", "docker-archive-test")
  503. if err != nil {
  504. t.Fatal(err)
  505. }
  506. defer os.RemoveAll(tempFolder)
  507. destFolder := filepath.Join(tempFolder, "dest")
  508. err = os.MkdirAll(destFolder, 0o740)
  509. if err != nil {
  510. t.Fatal(err)
  511. }
  512. invalidFile := filepath.Join(tempFolder, "doesnotexists")
  513. err = defaultCopyFileWithTar(invalidFile, destFolder)
  514. if err == nil {
  515. t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
  516. }
  517. }
  518. func TestCopyFileWithTarInexistentDestWillCreateIt(t *testing.T) {
  519. tempFolder, err := os.MkdirTemp("", "docker-archive-test")
  520. if err != nil {
  521. t.Fatal(nil)
  522. }
  523. defer os.RemoveAll(tempFolder)
  524. srcFile := filepath.Join(tempFolder, "src")
  525. inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists")
  526. f, err := os.Create(srcFile)
  527. if assert.Check(t, err) {
  528. _ = f.Close()
  529. }
  530. err = defaultCopyFileWithTar(srcFile, inexistentDestFolder)
  531. if err != nil {
  532. t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
  533. }
  534. _, err = os.Stat(inexistentDestFolder)
  535. if err != nil {
  536. t.Fatalf("CopyWithTar with an inexistent folder should create it.")
  537. }
  538. // FIXME Test the src file and content
  539. }
  540. func TestCopyFileWithTarSrcFolder(t *testing.T) {
  541. folder, err := os.MkdirTemp("", "docker-archive-copyfilewithtar-test")
  542. if err != nil {
  543. t.Fatal(err)
  544. }
  545. defer os.RemoveAll(folder)
  546. dest := filepath.Join(folder, "dest")
  547. src := filepath.Join(folder, "srcfolder")
  548. err = os.MkdirAll(src, 0o740)
  549. if err != nil {
  550. t.Fatal(err)
  551. }
  552. err = os.MkdirAll(dest, 0o740)
  553. if err != nil {
  554. t.Fatal(err)
  555. }
  556. err = defaultCopyFileWithTar(src, dest)
  557. if err == nil {
  558. t.Fatalf("CopyFileWithTar should throw an error with a folder.")
  559. }
  560. }
  561. func TestCopyFileWithTarSrcFile(t *testing.T) {
  562. folder, err := os.MkdirTemp("", "docker-archive-test")
  563. if err != nil {
  564. t.Fatal(err)
  565. }
  566. defer os.RemoveAll(folder)
  567. dest := filepath.Join(folder, "dest")
  568. srcFolder := filepath.Join(folder, "src")
  569. src := filepath.Join(folder, filepath.Join("src", "src"))
  570. err = os.MkdirAll(srcFolder, 0o740)
  571. if err != nil {
  572. t.Fatal(err)
  573. }
  574. err = os.MkdirAll(dest, 0o740)
  575. if err != nil {
  576. t.Fatal(err)
  577. }
  578. os.WriteFile(src, []byte("content"), 0o777)
  579. err = defaultCopyWithTar(src, dest+"/")
  580. if err != nil {
  581. t.Fatalf("archiver.CopyFileWithTar shouldn't throw an error, %s.", err)
  582. }
  583. _, err = os.Stat(dest)
  584. if err != nil {
  585. t.Fatalf("Destination folder should contain the source file but did not.")
  586. }
  587. }
  588. func TestTarFiles(t *testing.T) {
  589. // try without hardlinks
  590. if err := checkNoChanges(1000, false); err != nil {
  591. t.Fatal(err)
  592. }
  593. // try with hardlinks
  594. if err := checkNoChanges(1000, true); err != nil {
  595. t.Fatal(err)
  596. }
  597. }
  598. func checkNoChanges(fileNum int, hardlinks bool) error {
  599. srcDir, err := os.MkdirTemp("", "docker-test-srcDir")
  600. if err != nil {
  601. return err
  602. }
  603. defer os.RemoveAll(srcDir)
  604. destDir, err := os.MkdirTemp("", "docker-test-destDir")
  605. if err != nil {
  606. return err
  607. }
  608. defer os.RemoveAll(destDir)
  609. _, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks)
  610. if err != nil {
  611. return err
  612. }
  613. err = defaultTarUntar(srcDir, destDir)
  614. if err != nil {
  615. return err
  616. }
  617. changes, err := ChangesDirs(destDir, srcDir)
  618. if err != nil {
  619. return err
  620. }
  621. if len(changes) > 0 {
  622. return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes))
  623. }
  624. return nil
  625. }
  626. func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) {
  627. archive, err := TarWithOptions(origin, options)
  628. if err != nil {
  629. t.Fatal(err)
  630. }
  631. defer archive.Close()
  632. buf := make([]byte, 10)
  633. if _, err := archive.Read(buf); err != nil {
  634. return nil, err
  635. }
  636. wrap := io.MultiReader(bytes.NewReader(buf), archive)
  637. detectedCompression := DetectCompression(buf)
  638. compression := options.Compression
  639. if detectedCompression.Extension() != compression.Extension() {
  640. return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
  641. }
  642. tmp, err := os.MkdirTemp("", "docker-test-untar")
  643. if err != nil {
  644. return nil, err
  645. }
  646. defer os.RemoveAll(tmp)
  647. if err := Untar(wrap, tmp, nil); err != nil {
  648. return nil, err
  649. }
  650. if _, err := os.Stat(tmp); err != nil {
  651. return nil, err
  652. }
  653. return ChangesDirs(origin, tmp)
  654. }
  655. func TestDetectCompressionZstd(t *testing.T) {
  656. // test zstd compression without skippable frames.
  657. compressedData := []byte{
  658. 0x28, 0xb5, 0x2f, 0xfd, // magic number of Zstandard frame: 0xFD2FB528
  659. 0x04, 0x00, 0x31, 0x00, 0x00, // frame header
  660. 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, // data block "docker"
  661. 0x16, 0x0e, 0x21, 0xc3, // content checksum
  662. }
  663. compression := DetectCompression(compressedData)
  664. if compression != Zstd {
  665. t.Fatal("Unexpected compression")
  666. }
  667. // test zstd compression with skippable frames.
  668. hex := []byte{
  669. 0x50, 0x2a, 0x4d, 0x18, // magic number of skippable frame: 0x184D2A50 to 0x184D2A5F
  670. 0x04, 0x00, 0x00, 0x00, // frame size
  671. 0x5d, 0x00, 0x00, 0x00, // user data
  672. 0x28, 0xb5, 0x2f, 0xfd, // magic number of Zstandard frame: 0xFD2FB528
  673. 0x04, 0x00, 0x31, 0x00, 0x00, // frame header
  674. 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, // data block "docker"
  675. 0x16, 0x0e, 0x21, 0xc3, // content checksum
  676. }
  677. compression = DetectCompression(hex)
  678. if compression != Zstd {
  679. t.Fatal("Unexpected compression")
  680. }
  681. }
  682. func TestTarUntar(t *testing.T) {
  683. origin, err := os.MkdirTemp("", "docker-test-untar-origin")
  684. if err != nil {
  685. t.Fatal(err)
  686. }
  687. defer os.RemoveAll(origin)
  688. if err := os.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0o700); err != nil {
  689. t.Fatal(err)
  690. }
  691. if err := os.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0o700); err != nil {
  692. t.Fatal(err)
  693. }
  694. if err := os.WriteFile(filepath.Join(origin, "3"), []byte("will be ignored"), 0o700); err != nil {
  695. t.Fatal(err)
  696. }
  697. for _, c := range []Compression{
  698. Uncompressed,
  699. Gzip,
  700. } {
  701. changes, err := tarUntar(t, origin, &TarOptions{
  702. Compression: c,
  703. ExcludePatterns: []string{"3"},
  704. })
  705. if err != nil {
  706. t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
  707. }
  708. if len(changes) != 1 || changes[0].Path != string(filepath.Separator)+"3" {
  709. t.Fatalf("Unexpected differences after tarUntar: %v", changes)
  710. }
  711. }
  712. }
  713. func TestTarWithOptionsChownOptsAlwaysOverridesIdPair(t *testing.T) {
  714. origin, err := os.MkdirTemp("", "docker-test-tar-chown-opt")
  715. assert.NilError(t, err)
  716. defer os.RemoveAll(origin)
  717. filePath := filepath.Join(origin, "1")
  718. err = os.WriteFile(filePath, []byte("hello world"), 0o700)
  719. assert.NilError(t, err)
  720. idMaps := []idtools.IDMap{
  721. 0: {
  722. ContainerID: 0,
  723. HostID: 0,
  724. Size: 65536,
  725. },
  726. 1: {
  727. ContainerID: 0,
  728. HostID: 100000,
  729. Size: 65536,
  730. },
  731. }
  732. cases := []struct {
  733. opts *TarOptions
  734. expectedUID int
  735. expectedGID int
  736. }{
  737. {&TarOptions{ChownOpts: &idtools.Identity{UID: 1337, GID: 42}}, 1337, 42},
  738. {&TarOptions{ChownOpts: &idtools.Identity{UID: 100001, GID: 100001}, IDMap: idtools.IdentityMapping{UIDMaps: idMaps, GIDMaps: idMaps}}, 100001, 100001},
  739. {&TarOptions{ChownOpts: &idtools.Identity{UID: 0, GID: 0}, NoLchown: false}, 0, 0},
  740. {&TarOptions{ChownOpts: &idtools.Identity{UID: 1, GID: 1}, NoLchown: true}, 1, 1},
  741. {&TarOptions{ChownOpts: &idtools.Identity{UID: 1000, GID: 1000}, NoLchown: true}, 1000, 1000},
  742. }
  743. for _, tc := range cases {
  744. tc := tc
  745. t.Run("", func(t *testing.T) {
  746. reader, err := TarWithOptions(filePath, tc.opts)
  747. assert.NilError(t, err)
  748. tr := tar.NewReader(reader)
  749. defer reader.Close()
  750. for {
  751. hdr, err := tr.Next()
  752. if err == io.EOF {
  753. // end of tar archive
  754. break
  755. }
  756. assert.NilError(t, err)
  757. assert.Check(t, is.Equal(hdr.Uid, tc.expectedUID), "Uid equals expected value")
  758. assert.Check(t, is.Equal(hdr.Gid, tc.expectedGID), "Gid equals expected value")
  759. }
  760. })
  761. }
  762. }
  763. func TestTarWithOptions(t *testing.T) {
  764. origin, err := os.MkdirTemp("", "docker-test-untar-origin")
  765. if err != nil {
  766. t.Fatal(err)
  767. }
  768. if _, err := os.MkdirTemp(origin, "folder"); err != nil {
  769. t.Fatal(err)
  770. }
  771. defer os.RemoveAll(origin)
  772. if err := os.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0o700); err != nil {
  773. t.Fatal(err)
  774. }
  775. if err := os.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0o700); err != nil {
  776. t.Fatal(err)
  777. }
  778. cases := []struct {
  779. opts *TarOptions
  780. numChanges int
  781. }{
  782. {&TarOptions{IncludeFiles: []string{"1"}}, 2},
  783. {&TarOptions{ExcludePatterns: []string{"2"}}, 1},
  784. {&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2},
  785. {&TarOptions{IncludeFiles: []string{"1", "1"}}, 2},
  786. {&TarOptions{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 4},
  787. }
  788. for _, testCase := range cases {
  789. changes, err := tarUntar(t, origin, testCase.opts)
  790. if err != nil {
  791. t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err)
  792. }
  793. if len(changes) != testCase.numChanges {
  794. t.Errorf("Expected %d changes, got %d for %+v:",
  795. testCase.numChanges, len(changes), testCase.opts)
  796. }
  797. }
  798. }
  799. // Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz
  800. // use PAX Global Extended Headers.
  801. // Failing prevents the archives from being uncompressed during ADD
  802. func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) {
  803. hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader}
  804. tmpDir, err := os.MkdirTemp("", "docker-test-archive-pax-test")
  805. if err != nil {
  806. t.Fatal(err)
  807. }
  808. defer os.RemoveAll(tmpDir)
  809. err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, nil)
  810. if err != nil {
  811. t.Fatal(err)
  812. }
  813. }
  814. // Some tar have both GNU specific (huge uid) and Ustar specific (long name) things.
  815. // Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work.
  816. func TestUntarUstarGnuConflict(t *testing.T) {
  817. f, err := os.Open("testdata/broken.tar")
  818. if err != nil {
  819. t.Fatal(err)
  820. }
  821. defer f.Close()
  822. found := false
  823. tr := tar.NewReader(f)
  824. // Iterate through the files in the archive.
  825. for {
  826. hdr, err := tr.Next()
  827. if err == io.EOF {
  828. // end of tar archive
  829. break
  830. }
  831. if err != nil {
  832. t.Fatal(err)
  833. }
  834. if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" {
  835. found = true
  836. break
  837. }
  838. }
  839. if !found {
  840. t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm")
  841. }
  842. }
  843. func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
  844. fileData := []byte("fooo")
  845. for n := 0; n < numberOfFiles; n++ {
  846. fileName := fmt.Sprintf("file-%d", n)
  847. if err := os.WriteFile(filepath.Join(targetPath, fileName), fileData, 0o700); err != nil {
  848. return 0, err
  849. }
  850. if makeLinks {
  851. if err := os.Link(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil {
  852. return 0, err
  853. }
  854. }
  855. }
  856. totalSize := numberOfFiles * len(fileData)
  857. return totalSize, nil
  858. }
  859. func BenchmarkTarUntar(b *testing.B) {
  860. origin, err := os.MkdirTemp("", "docker-test-untar-origin")
  861. if err != nil {
  862. b.Fatal(err)
  863. }
  864. tempDir, err := os.MkdirTemp("", "docker-test-untar-destination")
  865. if err != nil {
  866. b.Fatal(err)
  867. }
  868. target := filepath.Join(tempDir, "dest")
  869. n, err := prepareUntarSourceDirectory(100, origin, false)
  870. if err != nil {
  871. b.Fatal(err)
  872. }
  873. defer os.RemoveAll(origin)
  874. defer os.RemoveAll(tempDir)
  875. b.ResetTimer()
  876. b.SetBytes(int64(n))
  877. for n := 0; n < b.N; n++ {
  878. err := defaultTarUntar(origin, target)
  879. if err != nil {
  880. b.Fatal(err)
  881. }
  882. os.RemoveAll(target)
  883. }
  884. }
  885. func BenchmarkTarUntarWithLinks(b *testing.B) {
  886. origin, err := os.MkdirTemp("", "docker-test-untar-origin")
  887. if err != nil {
  888. b.Fatal(err)
  889. }
  890. tempDir, err := os.MkdirTemp("", "docker-test-untar-destination")
  891. if err != nil {
  892. b.Fatal(err)
  893. }
  894. target := filepath.Join(tempDir, "dest")
  895. n, err := prepareUntarSourceDirectory(100, origin, true)
  896. if err != nil {
  897. b.Fatal(err)
  898. }
  899. defer os.RemoveAll(origin)
  900. defer os.RemoveAll(tempDir)
  901. b.ResetTimer()
  902. b.SetBytes(int64(n))
  903. for n := 0; n < b.N; n++ {
  904. err := defaultTarUntar(origin, target)
  905. if err != nil {
  906. b.Fatal(err)
  907. }
  908. os.RemoveAll(target)
  909. }
  910. }
  911. func TestUntarInvalidFilenames(t *testing.T) {
  912. for i, headers := range [][]*tar.Header{
  913. {
  914. {
  915. Name: "../victim/dotdot",
  916. Typeflag: tar.TypeReg,
  917. Mode: 0o644,
  918. },
  919. },
  920. {
  921. {
  922. // Note the leading slash
  923. Name: "/../victim/slash-dotdot",
  924. Typeflag: tar.TypeReg,
  925. Mode: 0o644,
  926. },
  927. },
  928. } {
  929. if err := testBreakout("untar", "docker-TestUntarInvalidFilenames", headers); err != nil {
  930. t.Fatalf("i=%d. %v", i, err)
  931. }
  932. }
  933. }
  934. func TestUntarHardlinkToSymlink(t *testing.T) {
  935. skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
  936. for i, headers := range [][]*tar.Header{
  937. {
  938. {
  939. Name: "symlink1",
  940. Typeflag: tar.TypeSymlink,
  941. Linkname: "regfile",
  942. Mode: 0o644,
  943. },
  944. {
  945. Name: "symlink2",
  946. Typeflag: tar.TypeLink,
  947. Linkname: "symlink1",
  948. Mode: 0o644,
  949. },
  950. {
  951. Name: "regfile",
  952. Typeflag: tar.TypeReg,
  953. Mode: 0o644,
  954. },
  955. },
  956. } {
  957. if err := testBreakout("untar", "docker-TestUntarHardlinkToSymlink", headers); err != nil {
  958. t.Fatalf("i=%d. %v", i, err)
  959. }
  960. }
  961. }
  962. func TestUntarInvalidHardlink(t *testing.T) {
  963. for i, headers := range [][]*tar.Header{
  964. { // try reading victim/hello (../)
  965. {
  966. Name: "dotdot",
  967. Typeflag: tar.TypeLink,
  968. Linkname: "../victim/hello",
  969. Mode: 0o644,
  970. },
  971. },
  972. { // try reading victim/hello (/../)
  973. {
  974. Name: "slash-dotdot",
  975. Typeflag: tar.TypeLink,
  976. // Note the leading slash
  977. Linkname: "/../victim/hello",
  978. Mode: 0o644,
  979. },
  980. },
  981. { // try writing victim/file
  982. {
  983. Name: "loophole-victim",
  984. Typeflag: tar.TypeLink,
  985. Linkname: "../victim",
  986. Mode: 0o755,
  987. },
  988. {
  989. Name: "loophole-victim/file",
  990. Typeflag: tar.TypeReg,
  991. Mode: 0o644,
  992. },
  993. },
  994. { // try reading victim/hello (hardlink, symlink)
  995. {
  996. Name: "loophole-victim",
  997. Typeflag: tar.TypeLink,
  998. Linkname: "../victim",
  999. Mode: 0o755,
  1000. },
  1001. {
  1002. Name: "symlink",
  1003. Typeflag: tar.TypeSymlink,
  1004. Linkname: "loophole-victim/hello",
  1005. Mode: 0o644,
  1006. },
  1007. },
  1008. { // Try reading victim/hello (hardlink, hardlink)
  1009. {
  1010. Name: "loophole-victim",
  1011. Typeflag: tar.TypeLink,
  1012. Linkname: "../victim",
  1013. Mode: 0o755,
  1014. },
  1015. {
  1016. Name: "hardlink",
  1017. Typeflag: tar.TypeLink,
  1018. Linkname: "loophole-victim/hello",
  1019. Mode: 0o644,
  1020. },
  1021. },
  1022. { // Try removing victim directory (hardlink)
  1023. {
  1024. Name: "loophole-victim",
  1025. Typeflag: tar.TypeLink,
  1026. Linkname: "../victim",
  1027. Mode: 0o755,
  1028. },
  1029. {
  1030. Name: "loophole-victim",
  1031. Typeflag: tar.TypeReg,
  1032. Mode: 0o644,
  1033. },
  1034. },
  1035. } {
  1036. if err := testBreakout("untar", "docker-TestUntarInvalidHardlink", headers); err != nil {
  1037. t.Fatalf("i=%d. %v", i, err)
  1038. }
  1039. }
  1040. }
  1041. func TestUntarInvalidSymlink(t *testing.T) {
  1042. for i, headers := range [][]*tar.Header{
  1043. { // try reading victim/hello (../)
  1044. {
  1045. Name: "dotdot",
  1046. Typeflag: tar.TypeSymlink,
  1047. Linkname: "../victim/hello",
  1048. Mode: 0o644,
  1049. },
  1050. },
  1051. { // try reading victim/hello (/../)
  1052. {
  1053. Name: "slash-dotdot",
  1054. Typeflag: tar.TypeSymlink,
  1055. // Note the leading slash
  1056. Linkname: "/../victim/hello",
  1057. Mode: 0o644,
  1058. },
  1059. },
  1060. { // try writing victim/file
  1061. {
  1062. Name: "loophole-victim",
  1063. Typeflag: tar.TypeSymlink,
  1064. Linkname: "../victim",
  1065. Mode: 0o755,
  1066. },
  1067. {
  1068. Name: "loophole-victim/file",
  1069. Typeflag: tar.TypeReg,
  1070. Mode: 0o644,
  1071. },
  1072. },
  1073. { // try reading victim/hello (symlink, symlink)
  1074. {
  1075. Name: "loophole-victim",
  1076. Typeflag: tar.TypeSymlink,
  1077. Linkname: "../victim",
  1078. Mode: 0o755,
  1079. },
  1080. {
  1081. Name: "symlink",
  1082. Typeflag: tar.TypeSymlink,
  1083. Linkname: "loophole-victim/hello",
  1084. Mode: 0o644,
  1085. },
  1086. },
  1087. { // try reading victim/hello (symlink, hardlink)
  1088. {
  1089. Name: "loophole-victim",
  1090. Typeflag: tar.TypeSymlink,
  1091. Linkname: "../victim",
  1092. Mode: 0o755,
  1093. },
  1094. {
  1095. Name: "hardlink",
  1096. Typeflag: tar.TypeLink,
  1097. Linkname: "loophole-victim/hello",
  1098. Mode: 0o644,
  1099. },
  1100. },
  1101. { // try removing victim directory (symlink)
  1102. {
  1103. Name: "loophole-victim",
  1104. Typeflag: tar.TypeSymlink,
  1105. Linkname: "../victim",
  1106. Mode: 0o755,
  1107. },
  1108. {
  1109. Name: "loophole-victim",
  1110. Typeflag: tar.TypeReg,
  1111. Mode: 0o644,
  1112. },
  1113. },
  1114. { // try writing to victim/newdir/newfile with a symlink in the path
  1115. {
  1116. // this header needs to be before the next one, or else there is an error
  1117. Name: "dir/loophole",
  1118. Typeflag: tar.TypeSymlink,
  1119. Linkname: "../../victim",
  1120. Mode: 0o755,
  1121. },
  1122. {
  1123. Name: "dir/loophole/newdir/newfile",
  1124. Typeflag: tar.TypeReg,
  1125. Mode: 0o644,
  1126. },
  1127. },
  1128. } {
  1129. if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil {
  1130. t.Fatalf("i=%d. %v", i, err)
  1131. }
  1132. }
  1133. }
  1134. func TestTempArchiveCloseMultipleTimes(t *testing.T) {
  1135. reader := io.NopCloser(strings.NewReader("hello"))
  1136. tempArchive, err := NewTempArchive(reader, "")
  1137. assert.NilError(t, err)
  1138. buf := make([]byte, 10)
  1139. n, err := tempArchive.Read(buf)
  1140. assert.NilError(t, err)
  1141. if n != 5 {
  1142. t.Fatalf("Expected to read 5 bytes. Read %d instead", n)
  1143. }
  1144. for i := 0; i < 3; i++ {
  1145. if err = tempArchive.Close(); err != nil {
  1146. t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err)
  1147. }
  1148. }
  1149. }
  1150. // TestXGlobalNoParent is a regression test to check parent directories are not created for PAX headers
  1151. func TestXGlobalNoParent(t *testing.T) {
  1152. buf := &bytes.Buffer{}
  1153. w := tar.NewWriter(buf)
  1154. err := w.WriteHeader(&tar.Header{
  1155. Name: "foo/bar",
  1156. Typeflag: tar.TypeXGlobalHeader,
  1157. })
  1158. assert.NilError(t, err)
  1159. tmpDir, err := os.MkdirTemp("", "pax-test")
  1160. assert.NilError(t, err)
  1161. defer os.RemoveAll(tmpDir)
  1162. err = Untar(buf, tmpDir, nil)
  1163. assert.NilError(t, err)
  1164. _, err = os.Lstat(filepath.Join(tmpDir, "foo"))
  1165. assert.Check(t, err != nil)
  1166. assert.Check(t, errors.Is(err, os.ErrNotExist))
  1167. }
  1168. // TestImpliedDirectoryPermissions ensures that directories implied by paths in the tar file, but without their own
  1169. // header entries are created recursively with the default mode (permissions) stored in ImpliedDirectoryMode. This test
  1170. // also verifies that the permissions of explicit directories are respected.
  1171. func TestImpliedDirectoryPermissions(t *testing.T) {
  1172. skip.If(t, runtime.GOOS == "windows", "skipping test that requires Unix permissions")
  1173. buf := &bytes.Buffer{}
  1174. headers := []tar.Header{{
  1175. Name: "deeply/nested/and/implied",
  1176. }, {
  1177. Name: "explicit/",
  1178. Mode: 0o644,
  1179. }, {
  1180. Name: "explicit/permissions/",
  1181. Mode: 0o600,
  1182. }, {
  1183. Name: "explicit/permissions/specified",
  1184. Mode: 0o400,
  1185. }}
  1186. w := tar.NewWriter(buf)
  1187. for _, header := range headers {
  1188. err := w.WriteHeader(&header)
  1189. assert.NilError(t, err)
  1190. }
  1191. tmpDir := t.TempDir()
  1192. err := Untar(buf, tmpDir, nil)
  1193. assert.NilError(t, err)
  1194. assertMode := func(path string, expected uint32) {
  1195. t.Helper()
  1196. stat, err := os.Lstat(filepath.Join(tmpDir, path))
  1197. assert.Check(t, err)
  1198. assert.Check(t, is.Equal(stat.Mode().Perm(), fs.FileMode(expected)))
  1199. }
  1200. assertMode("deeply", ImpliedDirectoryMode)
  1201. assertMode("deeply/nested", ImpliedDirectoryMode)
  1202. assertMode("deeply/nested/and", ImpliedDirectoryMode)
  1203. assertMode("explicit", 0o644)
  1204. assertMode("explicit/permissions", 0o600)
  1205. assertMode("explicit/permissions/specified", 0o400)
  1206. }
  1207. func TestReplaceFileTarWrapper(t *testing.T) {
  1208. filesInArchive := 20
  1209. testcases := []struct {
  1210. doc string
  1211. filename string
  1212. modifier TarModifierFunc
  1213. expected string
  1214. fileCount int
  1215. }{
  1216. {
  1217. doc: "Modifier creates a new file",
  1218. filename: "newfile",
  1219. modifier: createModifier(t),
  1220. expected: "the new content",
  1221. fileCount: filesInArchive + 1,
  1222. },
  1223. {
  1224. doc: "Modifier replaces a file",
  1225. filename: "file-2",
  1226. modifier: createOrReplaceModifier,
  1227. expected: "the new content",
  1228. fileCount: filesInArchive,
  1229. },
  1230. {
  1231. doc: "Modifier replaces the last file",
  1232. filename: fmt.Sprintf("file-%d", filesInArchive-1),
  1233. modifier: createOrReplaceModifier,
  1234. expected: "the new content",
  1235. fileCount: filesInArchive,
  1236. },
  1237. {
  1238. doc: "Modifier appends to a file",
  1239. filename: "file-3",
  1240. modifier: appendModifier,
  1241. expected: "fooo\nnext line",
  1242. fileCount: filesInArchive,
  1243. },
  1244. }
  1245. for _, testcase := range testcases {
  1246. sourceArchive, cleanup := buildSourceArchive(t, filesInArchive)
  1247. defer cleanup()
  1248. resultArchive := ReplaceFileTarWrapper(
  1249. sourceArchive,
  1250. map[string]TarModifierFunc{testcase.filename: testcase.modifier})
  1251. actual := readFileFromArchive(t, resultArchive, testcase.filename, testcase.fileCount, testcase.doc)
  1252. assert.Check(t, is.Equal(testcase.expected, actual), testcase.doc)
  1253. }
  1254. }
  1255. // TestPrefixHeaderReadable tests that files that could be created with the
  1256. // version of this package that was built with <=go17 are still readable.
  1257. func TestPrefixHeaderReadable(t *testing.T) {
  1258. skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
  1259. skip.If(t, userns.RunningInUserNS(), "skipping test that requires more than 010000000 UIDs, which is unlikely to be satisfied when running in userns")
  1260. // https://gist.github.com/stevvooe/e2a790ad4e97425896206c0816e1a882#file-out-go
  1261. testFile := []byte("\x1f\x8b\x08\x08\x44\x21\x68\x59\x00\x03\x74\x2e\x74\x61\x72\x00\x4b\xcb\xcf\x67\xa0\x35\x30\x80\x00\x86\x06\x10\x47\x01\xc1\x37\x40\x00\x54\xb6\xb1\xa1\xa9\x99\x09\x48\x25\x1d\x40\x69\x71\x49\x62\x91\x02\xe5\x76\xa1\x79\x84\x21\x91\xd6\x80\x72\xaf\x8f\x82\x51\x30\x0a\x46\x36\x00\x00\xf0\x1c\x1e\x95\x00\x06\x00\x00")
  1262. tmpDir, err := os.MkdirTemp("", "prefix-test")
  1263. assert.NilError(t, err)
  1264. defer os.RemoveAll(tmpDir)
  1265. err = Untar(bytes.NewReader(testFile), tmpDir, nil)
  1266. assert.NilError(t, err)
  1267. baseName := "foo"
  1268. pth := strings.Repeat("a", 100-len(baseName)) + "/" + baseName
  1269. _, err = os.Lstat(filepath.Join(tmpDir, pth))
  1270. assert.NilError(t, err)
  1271. }
  1272. func buildSourceArchive(t *testing.T, numberOfFiles int) (io.ReadCloser, func()) {
  1273. srcDir, err := os.MkdirTemp("", "docker-test-srcDir")
  1274. assert.NilError(t, err)
  1275. _, err = prepareUntarSourceDirectory(numberOfFiles, srcDir, false)
  1276. assert.NilError(t, err)
  1277. sourceArchive, err := TarWithOptions(srcDir, &TarOptions{})
  1278. assert.NilError(t, err)
  1279. return sourceArchive, func() {
  1280. os.RemoveAll(srcDir)
  1281. sourceArchive.Close()
  1282. }
  1283. }
  1284. func createOrReplaceModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  1285. return &tar.Header{
  1286. Mode: 0o600,
  1287. Typeflag: tar.TypeReg,
  1288. }, []byte("the new content"), nil
  1289. }
  1290. func createModifier(t *testing.T) TarModifierFunc {
  1291. return func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  1292. assert.Check(t, is.Nil(content))
  1293. return createOrReplaceModifier(path, header, content)
  1294. }
  1295. }
  1296. func appendModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
  1297. buffer := bytes.Buffer{}
  1298. if content != nil {
  1299. if _, err := buffer.ReadFrom(content); err != nil {
  1300. return nil, nil, err
  1301. }
  1302. }
  1303. buffer.WriteString("\nnext line")
  1304. return &tar.Header{Mode: 0o600, Typeflag: tar.TypeReg}, buffer.Bytes(), nil
  1305. }
  1306. func readFileFromArchive(t *testing.T, archive io.ReadCloser, name string, expectedCount int, doc string) string {
  1307. skip.If(t, runtime.GOOS != "windows" && os.Getuid() != 0, "skipping test that requires root")
  1308. destDir, err := os.MkdirTemp("", "docker-test-destDir")
  1309. assert.NilError(t, err)
  1310. defer os.RemoveAll(destDir)
  1311. err = Untar(archive, destDir, nil)
  1312. assert.NilError(t, err)
  1313. files, _ := os.ReadDir(destDir)
  1314. assert.Check(t, is.Len(files, expectedCount), doc)
  1315. content, err := os.ReadFile(filepath.Join(destDir, name))
  1316. assert.Check(t, err)
  1317. return string(content)
  1318. }
  1319. func TestDisablePigz(t *testing.T) {
  1320. _, err := exec.LookPath("unpigz")
  1321. if err != nil {
  1322. t.Log("Test will not check full path when Pigz not installed")
  1323. }
  1324. t.Setenv("MOBY_DISABLE_PIGZ", "true")
  1325. r := testDecompressStream(t, "gz", "gzip -f")
  1326. // For the bufio pool
  1327. outsideReaderCloserWrapper := r.(*ioutils.ReadCloserWrapper)
  1328. // For the context canceller
  1329. contextReaderCloserWrapper := outsideReaderCloserWrapper.Reader.(*ioutils.ReadCloserWrapper)
  1330. assert.Equal(t, reflect.TypeOf(contextReaderCloserWrapper.Reader), reflect.TypeOf(&gzip.Reader{}))
  1331. }
  1332. func TestPigz(t *testing.T) {
  1333. r := testDecompressStream(t, "gz", "gzip -f")
  1334. // For the bufio pool
  1335. outsideReaderCloserWrapper := r.(*ioutils.ReadCloserWrapper)
  1336. // For the context canceller
  1337. contextReaderCloserWrapper := outsideReaderCloserWrapper.Reader.(*ioutils.ReadCloserWrapper)
  1338. _, err := exec.LookPath("unpigz")
  1339. if err == nil {
  1340. t.Log("Tested whether Pigz is used, as it installed")
  1341. // For the command wait wrapper
  1342. cmdWaitCloserWrapper := contextReaderCloserWrapper.Reader.(*ioutils.ReadCloserWrapper)
  1343. assert.Equal(t, reflect.TypeOf(cmdWaitCloserWrapper.Reader), reflect.TypeOf(&io.PipeReader{}))
  1344. } else {
  1345. t.Log("Tested whether Pigz is not used, as it not installed")
  1346. assert.Equal(t, reflect.TypeOf(contextReaderCloserWrapper.Reader), reflect.TypeOf(&gzip.Reader{}))
  1347. }
  1348. }
  1349. func TestNosysFileInfo(t *testing.T) {
  1350. st, err := os.Stat("archive_test.go")
  1351. assert.NilError(t, err)
  1352. h, err := tar.FileInfoHeader(nosysFileInfo{st}, "")
  1353. assert.NilError(t, err)
  1354. assert.Check(t, h.Uname == "")
  1355. assert.Check(t, h.Gname == "")
  1356. }