Skip to content
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

Sys.isexecutable() unreliable on Windows #33212

Closed
staticfloat opened this issue Sep 10, 2019 · 18 comments
Closed

Sys.isexecutable() unreliable on Windows #33212

staticfloat opened this issue Sep 10, 2019 · 18 comments
Assignees
Labels
filesystem Underlying file system and functions that use it system:windows Affects only Windows
Milestone

Comments

@staticfloat
Copy link
Member

The current implementation of Sys.isexecutable() is broken on Windows; it does not properly query the relevant ACLs, and simply returns true if the file exists.

It is necessary for us to emulate the proper behavior here as git tree hashing requires knowing if a file is executable or not, and the native git tools on Windows do "the right thing", so in order to hash to the same value we will need to do the right thing as well.

The logic to do this is complex, but reducable. An example from cygwin is given here: https://github.com/Alexpux/Cygwin/blob/9c84bfd47922aad4881f80243320422b621c95dc/winsup/cygwin/sec_acl.cc#L636-L644

@StefanKarpinski StefanKarpinski added system:windows Affects only Windows filesystem Underlying file system and functions that use it labels Sep 10, 2019
@musm
Copy link
Contributor

musm commented Oct 9, 2019

@staticfloat I have a solution here, utilizing SHGetFileInfo (https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shgetfileinfoa) By obtaining the file's SHGFI_EXETYPE (https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shgetfileinfoa#return-value)

It can provide fine grain information on whether the file is:

Windows application.
MS-DOS .exe or .com file
Console application or .bat file

I didn't really look at the logic here, but it seems much complex than using the Windows API directly:

The logic to do this is complex, but reducable. An example from cygwin is given here: https://github.com/Alexpux/Cygwin/blob/9c84bfd47922aad4881f80243320422b621c95dc/winsup/cygwin/sec_acl.cc#L636-L644

Should I open a PR with the stated changes?

@StefanKarpinski
Copy link
Member

@vtjnash, does this seem like a reasonable approach to you?

@staticfloat
Copy link
Member Author

If the docs are accurate then yes, this does seem reasonable to me. Good find, @musm! I think a small PoC would be really helpful to both myself and Jameson, and doesn't look too hard to put together.

@musm
Copy link
Contributor

musm commented Oct 9, 2019

I can just open a PR with a working implementation which I am close to finishing

@vtjnash
Copy link
Member

vtjnash commented Dec 12, 2019

This would require having the isexecutable check calling the AccessCheck function (https://docs.microsoft.com/en-us/windows/win32/secauthz/verifying-client-access-with-acls-in-c--). We could also implement this in uv_fs_access, and then use that as a cross-platform solution.

@staticfloat
Copy link
Member Author

@vtjnash which would you prefer I do? I know you have performance concerns about this, would you prefer that we do this in C whenever we call uv_fs_access() or should it be controllable from Julia somehow?

@vtjnash
Copy link
Member

vtjnash commented Dec 12, 2019

Oh, I don't care about performance of this. If you mean TOCTOU, this is instantly invalid, so it could never be never fast enough. Implementation in C might be easier because it takes so many arguments, or maybe not.

@staticfloat
Copy link
Member Author

Okay; I'll work up a patch to libuv and request your attention when it's ready

@staticfloat
Copy link
Member Author

WIP branches:

So far I'm having a hard time testing this; I actually am not sure what "executable" means in Windows; when I create normal files on Windows, right-click on them in Explorer and go to the Security tab, they have the Read & Execute permission enabled for the Administrators group. Does that mean that by default, most files have the executable permission set?

@StefanKarpinski
Copy link
Member

What does git do on Windows? Or is that a meaningless question because it always runs in some UNIX emulation environment? The property we want (for ourselves—not sure if this is appropriate for libuv) is that when we unpack something, we can compute the git hash correctly. Frankly, we could make everything executable and just use some other bit of irrelevant metadata, but it seems like there must be a bit that also means the right thing to the operating system.

@staticfloat
Copy link
Member Author

Okay I've actually got it working, but now I have to fix uv_fs_chmod() to also set the ACL when we are attempting to do so, otherwise we have no way of controlling the executable bit on Windows.

@StefanKarpinski
Copy link
Member

Brilliant!

@staticfloat
Copy link
Member Author

Major progress. I bashed my head against the wall for a few hours, wondering why on earth certain API calls were failing, until I realized that half of the calls in win32 return 0 on success, whereas the other half return 0 on error. After rolling my eyes hard enough to critically fail my next saving throw, I got chmod() working, for a very specific value of "working". You can try this out on your favorite windows machine by building the Julia branch. You can also see the libuv diff.

The implementation is fairly straightforward: I augment fs__access() on windows to use CheckAccess() to determine if we have executable access to a file, and I augment fs__chmod() on windows to set FILE_EXECUTE ACL permissions for the current user appropriately. This allows us to do the following:

$ cat test.jl
rm("foo")

touch("foo")
@show Sys.isexecutable("foo")
chmod("foo", 0o0666)
@show Sys.isexecutable("foo")
chmod("foo", 0o0777)
@show Sys.isexecutable("foo")

$ ~/src/julia/usr/bin/julia.exe test.jl
Sys.isexecutable("foo") = true
Sys.isexecutable("foo") = false
Sys.isexecutable("foo") = true

Party time right? Nope, not quite yet. Observe:

$ touch foo
$ ls -la foo
-rw-r--r--+ 1 Administrator None 0 Dec 17 04:21 foo
$ ~/src/julia/usr/bin/julia.exe -e '@show Sys.isexecutable("foo")'
Sys.isexecutable("foo") = false

So far so good, however:

$ ~/src/julia/usr/bin/julia.exe -e 'chmod("foo", 0o0777)'
$ ~/src/julia/usr/bin/julia.exe -e '@show Sys.isexecutable("foo")'
Sys.isexecutable("foo") = false

What happens is that the touch command in mingw64 initializes an explicit deny on executable permissions for the Administrators group:
image
Apparently, DENY ACL entries have priority over ALLOW ACL entries, which means that you need to remove the Administrators ACL DENY entry that conflicts with the Administrator ACL ALLOW entry for it to take effect. Wonderful.

I could write a tool that iterates over the current ACL, finds conflicting entries, and modifies them, but I want to make sure that that is the correct way of doing it before chipping away at it, since it is tiresome and annoying. I am glad that this is half-way working though.

So, current caveats for this solution:

  • chmod() can be defeated by conflicting ACL entries, in a very non-obvious way.
  • This only pays attention to S_IXUSR, it doesn't do anything with S_IXGRP or S_IXOTH. Doing S_IXOTH would actually be quite easy, and S_IXGRP would involve just looking up the owning group and modifying that as well, I think.

@JeffBezanson JeffBezanson modified the milestones: 1.4, 1.5 Dec 17, 2019
@staticfloat
Copy link
Member Author

staticfloat commented Dec 30, 2019

A week of vacation did wonders for my desire to return to this, and I think I managed to get something fully working today: JuliaLang/libuv#6

I did end up writing the loops to iterate over all ACL entries, find SID's that the current user belongs to (that aren't the user's own SID nor the user's primary group SID) and apply the "group" mode to those groups. It's possibly slightly heavier-handed than it should be (it always sets everything explicitly, doesn't bother with determining the possible ACL inheritance flows to minimize the number of explicit entries) but that's totally fine by me. We could upgrade fs__stat() to also give us something reasonable now that we know how this stuff works, but since that's not blocking anything, I vote just test this current implementation as best we can, and move on. :)

staticfloat added a commit to JuliaLang/Pkg.jl that referenced this issue Dec 30, 2019
…ee hashes

We need this to be more general than just skipping Windows; if we are on
Linux but using an NFS share or FAT32 filesystems, (for instance) we can
run into the same issues.  So we instead probe for the ability to have a
sane `chmod()`/`isexecutable()` run, and if that fails, we do not bother
to calculate git tree hashes.  This should transparently start to
calculate the proper git tree hashes on Windows once
JuliaLang/julia#33212 is fixed
staticfloat added a commit to JuliaLang/Pkg.jl that referenced this issue Dec 30, 2019
…ee hashes

We need this to be more general than just skipping Windows; if we are on
Linux but using an NFS share or FAT32 filesystems, (for instance) we can
run into the same issues.  So we instead probe for the ability to have a
sane `chmod()`/`isexecutable()` run, and if that fails, we do not bother
to calculate git tree hashes.  This should transparently start to
calculate the proper git tree hashes on Windows once
JuliaLang/julia#33212 is fixed
staticfloat added a commit to JuliaLang/Pkg.jl that referenced this issue Dec 31, 2019
…ee hashes

We need this to be more general than just skipping Windows; if we are on
Linux but using an NFS share or FAT32 filesystems, (for instance) we can
run into the same issues.  So we instead probe for the ability to have a
sane `chmod()`/`isexecutable()` run, and if that fails, we do not bother
to calculate git tree hashes.  This should transparently start to
calculate the proper git tree hashes on Windows once
JuliaLang/julia#33212 is fixed
staticfloat added a commit to JuliaLang/Pkg.jl that referenced this issue Dec 31, 2019
…ee hashes

We need this to be more general than just skipping Windows; if we are on
Linux but using an NFS share or FAT32 filesystems, (for instance) we can
run into the same issues.  So we instead probe for the ability to have a
sane `chmod()`/`isexecutable()` run, and if that fails, we do not bother
to calculate git tree hashes.  This should transparently start to
calculate the proper git tree hashes on Windows once
JuliaLang/julia#33212 is fixed
staticfloat added a commit to JuliaLang/Pkg.jl that referenced this issue Apr 28, 2020
…ee hashes

We need this to be more general than just skipping Windows; if we are on
Linux but using an NFS share or FAT32 filesystems, (for instance) we can
run into the same issues.  So we instead probe for the ability to have a
sane `chmod()`/`isexecutable()` run, and if that fails, we do not bother
to calculate git tree hashes.  This should transparently start to
calculate the proper git tree hashes on Windows once
JuliaLang/julia#33212 is fixed
staticfloat added a commit to JuliaLang/Pkg.jl that referenced this issue Apr 28, 2020
…ee hashes

We need this to be more general than just skipping Windows; if we are on
Linux but using an NFS share or FAT32 filesystems, (for instance) we can
run into the same issues.  So we instead probe for the ability to have a
sane `chmod()`/`isexecutable()` run, and if that fails, we do not bother
to calculate git tree hashes.  This should transparently start to
calculate the proper git tree hashes on Windows once
JuliaLang/julia#33212 is fixed
@martinholters
Copy link
Member

Fixed by #35625?

@StefanKarpinski
Copy link
Member

Seems so, but would be good to get @staticfloat's explicit approval.

@visr
Copy link
Contributor

visr commented May 15, 2020

From #35625

(Jeff) fixes #33212?
(Elliot) Yes. Finally. :)

Since #35625 is on the 1.5 milestone, will it get added to that release branch still? (#35846)

@staticfloat
Copy link
Member Author

Yep! I'm calling this fixed! :D

Hmmm, I don't think so. I think we decided that we're still not certain enough of the functionality here, and upstream has some notes on the implementation, so it may get tweaked. I'll remove it from the milestone.

staticfloat added a commit to JuliaLang/Pkg.jl that referenced this issue Jun 25, 2020
…ee hashes

We need this to be more general than just skipping Windows; if we are on
Linux but using an NFS share or FAT32 filesystems, (for instance) we can
run into the same issues.  So we instead probe for the ability to have a
sane `chmod()`/`isexecutable()` run, and if that fails, we do not bother
to calculate git tree hashes.  This should transparently start to
calculate the proper git tree hashes on Windows once
JuliaLang/julia#33212 is fixed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
filesystem Underlying file system and functions that use it system:windows Affects only Windows
Projects
None yet
Development

No branches or pull requests

7 participants