New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
shutil copy* unsafe on POSIX - they preserve setuid/setgit bits #61382
Comments
When copying the mode of a file with copy, copy2, copymode, copystat or copytree, all permission bits are copied (including setuid and setgit), but the owner of the file is not. This can be used for privilege escalation. An example: -rwSr--r-- 1 milko milko 0 фев 11 10:53 test1 shutil.copy("test1", "test2") -rwSr--r-- 1 root root 0 фев 11 10:53 test2 If test1 contained anything malicious, now the user milko can execute his malicious payload as root. Potential fixes:
The behaviour of copymode/copystat in this case is the same as |
Thanks for the report. I agree with your analysis. We should follow the behavior of cp and always strip off the suid/sgid bits in shutil.copy(). coreutil's cp removes the bits and doesn't handle source owner = destination owner special. There are other bits that may need special treatment, too. I'm going to check the sources of cp. |
cp removes three bits unless preserve ownership is enabled and some additional things are true. mode &= ~ (S_ISUID | S_ISGID | S_ISVTX) S_ISVTX is the sticky bit. |
While I agree that it’s a problem, I’m a bit uneasy about changing that back to 2.7. I’m pretty sure this would break numerous programs. |
Here is a patch for the issue with test and doc updates. I'm escalating the bug to release blocker to draw the attention of our RMs. |
Sorry for the extra noise. I got into a comment conflict with Hynek. Hynek, It may break system scripts that copy entire Unix systems with a recursive copytree(), though ... |
Yeah, I’m thinking about backup scripts etc. |
Here is a new patch with a new keyword argument preserve_sbits. Perhaps we use |
SGTM. I’d like an explicit warning on the security implications in the docs though. |
Shouldn't you try to make the permission removal atomic? Otherwise there's a window of opportunity to exploit the suid bit. |
Actually there's already a race even without setuid bit: http://bugs.python.org/issue15100 All metadat should be set atomically. |
Not blocking 2.7.4 as discussed on mailing list. |
Permissions bits are copied from the source file *after* all data has been copied to the destination file. copy() calls copyfile() followed by copymode() copyfile() doesn't create files with SUID. In fact it has 0666 & umask. In worst case the new file is readable and writable by every user. The new patch addresses the unlikely issue with os.open()ing the file with mask=0600. I could also add a create_mode argument to _io.FileIO() in order to make the permission bits of new files more flexible. Modules/_io/fileio.c hard codes mode as 0600. |
See also bpo-15795. It would be good to make shutil, zipfile and tarfile interfaces consistent. I think we need more graduated interface matching coretools. """
""" This means that we should add preserve_mode, preserve_ownership, preserve_time, etc parameters. preserve_ownership should control also copying of suid/sgid/sticky bits. copy()'s defaults will be preserve_mode=True, preserve_ownership=False, preserve_time=False, copy2()'s defaults (corresponding to "cp -p" behavior) will be preserve_mode=True, preserve_ownership=True, preserve_time=True. |
Should the patch be turned into a PR or should this be closed? |
I'll accept this into 3.4 and 3.5, if someone produces a PR and someone else reviews it. Given that the issue has already celebrated its fifth birthday I can't say I feel a lot of urgency about it. |
My current UI shows this as relevant *only* to 3.4 and 3.5. If it really has been fixed in 3.6, and the fix can't be backported, I think the risk of breaking backup programs is enough to argue for doing nothing more than a doc change. Anyone still using 3.4 (or even 3.5) *and* able to install from source is likely to be more upset by unexpected (and possibly silent) breakage of an existing process than new exploits of a 6 year old bug. That said, I'm probably the wrong person to verify which versions are affected, so consider this as only soft support for Release Manager to do so if this continues to languish. |
I am looking at this. Based on the comments from a historical perspective - copyfile() needs to be calling the copy_mode function before any copying actually occurs. As the dest is already open for writing it does not matter (on posix) Question: in copystat() there is a block that talks about chown() in the comments but in the code it only seems to be accessing chmod(). Should there (also) be a call to chown "if _SUPER"? _SUPER = _POSIX and os.geteuid() == 0 basically - mode becomes: # remove setuid, setgid and sticky bits if _POSIX and not _SUPER
mode = stat.S_IMODE(st.st_mode) & _REMOVE_HARMFUL_MASK if _POSIX and not _SUPER else stat.S_IMODE(st.st_mode) Comments? |
my bad: forgot the snippet I mentioned in the previous post:
|
I want to believe this can be resolved - without breakage on POSIX. Clarification: while Mac/OS falls under "posix" in python terms - maybe Short text: to proceed I think we should start with getting some My experience is that cp -r[pP] behaves the same as shutil.copy*() when I would appreciate someone helping me writing more extensive testing. We need to test:
I am working on a patch to address these different conditions. Ideally, the "not-owner" and "not-group" tests can be run ±±± Perspective ±±± If this is too much discussion, please reply with suggestions - privately - The issue seems unchanged since original posting. The original report states: ...snip... The behaviour of copymode/copystat in this case is the same as For clarity: GNU chmod states: --reference=RFILE Additionally, the chmod man page reminds us the "special bit" masking "... a directory's unmentioned set user and group ID bits are not affected" Additional comments discuss: And the comment/opinion that shutil.copy() should emulate cp (implies it seems shutil.copy2() is adding the -p (or -P if follow_symlinks=false) There was a modification to test_shutil.py suggested as part of a patch. ±±± + @unittest.skipUnless((os.name == "posix" and os.geteuid() != 0), class TestWhich(unittest.TestCase): Traceback (most recent call last):
File "/data/prj/python/git/python3-3.8/Lib/test/test_shutil.py", line
1491, in test_copy_remove_setuid
self.assertEqual(oct(mode), oct(harmless_mode))
AssertionError: '0o4500' != '0o500'
- 0o4500
? -
+ 0o500 On 8/15/2018 1:01 PM, Michael Felt wrote:
|
I don't understand this clarification:
AFAIK macOS should behave just like other posix-y platforms here. In particular, I've verified that cp(1) behaves the same as on other platforms: the SUID bit is stripped when copying a setuid file. Do you have a reason to assume that macOS is special here? P.S. macOS is spelled macOS, not Mac/OS |
On 16/08/2018 17:34, Ronald Oussoren wrote:
I was expecting or hoping macOS would behave as you describe but I was
|
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: