|
2 | 2 | import sys |
3 | 3 | import os |
4 | 4 | import io |
| 5 | +import tempfile |
5 | 6 | from hashlib import sha256 |
6 | 7 | from contextlib import contextmanager, ExitStack |
7 | 8 | from random import Random |
@@ -4304,6 +4305,105 @@ def test_chmod_outside_dir(self): |
4304 | 4305 | st_mode = cc.outerdir.stat().st_mode |
4305 | 4306 | self.assertNotEqual(st_mode & 0o777, 0o777) |
4306 | 4307 |
|
| 4308 | + @symlink_test |
| 4309 | + def test_chown_links_on_extract(self): |
| 4310 | + for link_type in (tarfile.SYMTYPE, tarfile.LNKTYPE): |
| 4311 | + buf = io.BytesIO() |
| 4312 | + with tarfile.open(fileobj=buf, mode='w') as tf: |
| 4313 | + # Create a regular file entry with uid/gid |
| 4314 | + info = tarfile.TarInfo(name="test.txt") |
| 4315 | + info.uid = 1337 |
| 4316 | + info.gid = 1337 |
| 4317 | + info.uname = "" |
| 4318 | + info.gname = "" |
| 4319 | + info.mode = 0o755 |
| 4320 | + tf.addfile(info, io.BytesIO(b"")) |
| 4321 | + |
| 4322 | + # Create a link to the normal file. |
| 4323 | + link_info = tarfile.TarInfo(name="link") |
| 4324 | + link_info.type = link_type |
| 4325 | + link_info.linkname = "test.txt" |
| 4326 | + link_info.uid = 1337 |
| 4327 | + link_info.gid = 1337 |
| 4328 | + link_info.uname = "" |
| 4329 | + link_info.gname = "" |
| 4330 | + link_info.mode = 0o644 |
| 4331 | + tf.addfile(link_info) |
| 4332 | + |
| 4333 | + buf.seek(0) |
| 4334 | + with ( |
| 4335 | + self.subTest(f"type={link_type!r}"), |
| 4336 | + tempfile.TemporaryDirectory() as tmpdir, |
| 4337 | + tarfile.open(fileobj=buf) as tar, |
| 4338 | + unittest.mock.patch("os.chown") as mock_chown, |
| 4339 | + unittest.mock.patch("os.lchown") as mock_lchown, |
| 4340 | + unittest.mock.patch("os.geteuid") as mock_geteuid, |
| 4341 | + ): |
| 4342 | + # Set UID to 0 so chown() is attempted. |
| 4343 | + mock_geteuid.return_value = 0 |
| 4344 | + tar.extract("link", path=tmpdir, filter='data') |
| 4345 | + extract_path = os.path.join(tmpdir, "link") |
| 4346 | + |
| 4347 | + if link_type == tarfile.SYMTYPE: |
| 4348 | + mock_chown.assert_not_called() |
| 4349 | + mock_lchown.assert_called_once_with(extract_path, -1, -1) |
| 4350 | + else: |
| 4351 | + mock_chown.assert_has_calls([ |
| 4352 | + unittest.mock.call(extract_path, -1, -1), |
| 4353 | + unittest.mock.call(extract_path, -1, -1) |
| 4354 | + ]) |
| 4355 | + mock_lchown.assert_not_called() |
| 4356 | + |
| 4357 | + @symlink_test |
| 4358 | + def test_chown_links_on_extractall(self): |
| 4359 | + for link_type in (tarfile.SYMTYPE, tarfile.LNKTYPE): |
| 4360 | + buf = io.BytesIO() |
| 4361 | + with tarfile.open(fileobj=buf, mode='w') as tf: |
| 4362 | + # Create a regular file entry with uid/gid |
| 4363 | + info = tarfile.TarInfo(name="test.txt") |
| 4364 | + info.uid = 1337 |
| 4365 | + info.gid = 1337 |
| 4366 | + info.uname = "" |
| 4367 | + info.gname = "" |
| 4368 | + info.mode = 0o755 |
| 4369 | + tf.addfile(info, io.BytesIO(b"")) |
| 4370 | + |
| 4371 | + # Create a link to the normal file. |
| 4372 | + link_info = tarfile.TarInfo(name="link") |
| 4373 | + link_info.type = link_type |
| 4374 | + link_info.linkname = "test.txt" |
| 4375 | + link_info.uid = 1337 |
| 4376 | + link_info.gid = 1337 |
| 4377 | + link_info.uname = "" |
| 4378 | + link_info.gname = "" |
| 4379 | + link_info.mode = 0o644 |
| 4380 | + tf.addfile(link_info) |
| 4381 | + |
| 4382 | + buf.seek(0) |
| 4383 | + with ( |
| 4384 | + self.subTest(f"type={link_type!r}"), |
| 4385 | + tempfile.TemporaryDirectory() as tmpdir, |
| 4386 | + tarfile.open(fileobj=buf) as tar, |
| 4387 | + unittest.mock.patch("os.chown") as mock_chown, |
| 4388 | + unittest.mock.patch("os.lchown") as mock_lchown, |
| 4389 | + unittest.mock.patch("os.geteuid") as mock_geteuid, |
| 4390 | + ): |
| 4391 | + # Set UID to 0 so chown() is attempted. |
| 4392 | + mock_geteuid.return_value = 0 |
| 4393 | + tar.extractall(path=tmpdir, filter='data') |
| 4394 | + extract_link_path = os.path.join(tmpdir, "link") |
| 4395 | + extract_file_path = os.path.join(tmpdir, "test.txt") |
| 4396 | + |
| 4397 | + if link_type == tarfile.SYMTYPE: |
| 4398 | + mock_chown.assert_called_once_with(extract_file_path, -1, -1) |
| 4399 | + mock_lchown.assert_called_once_with(extract_link_path, -1, -1) |
| 4400 | + else: |
| 4401 | + mock_chown.assert_has_calls([ |
| 4402 | + unittest.mock.call(extract_file_path, -1, -1), |
| 4403 | + unittest.mock.call(extract_link_path, -1, -1) |
| 4404 | + ]) |
| 4405 | + mock_lchown.assert_not_called() |
| 4406 | + |
4307 | 4407 | def test_link_fallback_normalizes(self): |
4308 | 4408 | # Make sure hardlink fallbacks work for non-normalized paths for all |
4309 | 4409 | # filters |
|
0 commit comments