File permissions and VSS failure

Joseph C. Nemeth jnemeth@palg.com
Wed Oct 18 04:06:00 GMT 2006


Summary:

I dropped a long way down the rabbit-hole on this one. I finally found the 
problem, and what initially seemed like a boneheaded Cygwin bug turns out 
to be an annoying fact of life. It has to do with file permissions and 
broken Windows software, specifically Visual Source Safe (VSS). But it also 
applies to most Windows software that attempts to modify file permissions.

I found the FAQs suggestive but not helpful enough, since they didn't lead 
me to understanding the problem well enough to work around it with 
confidence. I probably did not go far enough back in the archives, so if 
I'm ignoring a brilliant treatise on this subject, I apologize in advance.

Background:

I use tar to package a set of files on Unix boxes (multiple flavors), and 
'download' them to my NTFS WinXP Pro box under Cygwin, where I maintain 
them using Windows-style tools, including Microsoft Visual Source Safe. 
Then, I 'upload' them back to the Linux box, where they get compiled and 
executed.

When I 'download' files from Unix, the permissions somehow get messed up, 
and VSS balks with an error 'Access to file 'whatever' denied." The MS Help 
claims this is permissions or a file-locking problem. It turns out, it's a 
permissions problem, but it's deep in the bowels of the software.

Solution:

When Cygwin is done touching the files (e.g. tar), you MUST override the 
original Unix permissions and make the files writeable under Cygwin. This 
will cause VSS to complain mightily about writeable files in some 
instances, but you can either override the complaint manually in VSS, or 
write a simple Windows program to make all the affected files Read-Only, 
which you can use AFTER you've used Cygwin chmod to make the file writeable.

This solution also applies to any older Windows software that attempts to 
make a Read-Only file writeable.

Details:

I eventually reverse-engineered the mechanism that Cygwin uses to "store" 
its permissions on Windows NTFS. Given the number of queries on the FAQ 
about file permissions, I was surprised to find few authoritative or 
detailed responses.

Cygwin creates and uses three NTFS security ACLs on each file it handles, 
for three different MS security SID values: one is the well-known group SID 
'Everyone' (used for the Unix 'all' permissions), one is a group named 
'None' (used for the Unix group permissions), and one is a user equal to 
the current user (used for the Unix user permissions). Each ACL sets 
separate NTFS permissions on the file analogous to the Unix permissions 
bits, which not only store the permissions for display, but actually 
enforce them under NTFS.

The NTFS ACLs provide a lot of permissions for controlling access. For 
files and directories, the relevant ones are:

00000001 Read Data
00000002 Write Data
00000004 Append Data
00000008 Read Extended Attributes
00000010 Write Extended Attributes
00000020 Execute
00000040 Delete Subdirectory
00000080 Read Attributes
00000010 Write Attributes
00010000 Delete
00020000 Read DAC
00040000 Write DAC
00080000 Write Owner
00100000 Synchronize

00020088 is the minimal pattern for files, present when there are no Unix 
permissions. Without these (Read EA, Read Attr, Read DAC) you would not be 
able to determine whether you had permission to discover your permissions. 
The owner always has a slightly higher minimal set of  001F0110, which 
allows the owner to change ownership, delete the file, and change all 
permissions.

The Unix execute bit maps to 00000020 (Execute), read to 00000001 (Read 
Data), and write to 00000006 (Write and Append Data). So far, so good. 
Because of the way ACLs interact with each other, there can be a rather 
complex mess of 'grant' and 'deny' ACLs, but the use of the bits is pretty 
straightforward.

Now, here's the rub. There is also the "normal" attributes mask, which is 
what almost all of the generic Windows software, such as VSS, uses 
exclusively to determine whether the file is writeable. There is a single 
bit in the normal attributes called Read-Only. This is the bit reflected in 
the Properties dialog box for the file, as well as the file attributes 
acquired through Windows calls to GetFileAttributes() and 
GetFileAttributesEx(), and also the Windows POSIX library _stat() and 
_chmod() calls. None of these calls will tell you anything at all about the 
ACLs on the file, and therefore they will tell you nothing about the Cygwin 
file permissions. Getting into the NTFS ACL calls is the usual slog through 
the Microsoft maze of horrors, and is not for the faint of heart: see 
closing notes.

In particular, software like VSS will attempt to modify the file 
permissions so that it can manage the file. When a file is "checked out" 
under VSS, it is made writeable, meaning the Read-Only bit is cleared. When 
it is "checked in", the Read-Only bit is set. However, VSS does not make 
changes to the NTFS ACL permissions. I doubt that it even knows about them 
- it's very old, pre-NT software. All it knows is that it "takes control" 
of the write permissions, makes the file writeable, and then - when it 
tries to actually write - gets blocked by the NTFS ACL permissions that 
have been set up by Cygwin and fails.

This seems to be just a fact of life: VSS isn't going to fix this, and 
there is nothing Cygwin could or should do about it. The solution is to 
make the file always writeable under Cygwin, and then let VSS have its way 
with the Read-Only bit in the standard attributes. This is a little awkward 
when VSS expects to find the file in a Read-Only state after you've handled 
it with Cygwin, because when Cygwin makes the file writeable (with the 
ACLs) it also clears the Read-Only bit. However, you can then go through 
with a simple Windows program that calls the POSIX library _chmod() to make 
the file Read-Only: it won't affect the ACLs, but it will set the Read-Only 
bit and keep VSS happy.

Incidentally, setting the Read-Only bit this way will make all of the write 
permissions in Cygwin appear to go away, which is appropriate, since an 
attempt to write the Read-Only file will fail. However, if you clear the 
Read-Only bit, all of the write permissions will come back just exactly the 
way you had them before, because nothing was changed in the ACLs.

Closing Notes:

If you are as foolish as me and try to get into the Windows side of this, 
here are a few useful hints.

First, if you are running WinXP Professional, you can gain access to the 
ACLs through the Windows interface, but it's tricky. They ship WinXP Pro 
with 'Simple Sharing' turned ON by default, which prevents you from even 
looking at the ACLs. To turn this OFF, you go to:

Start->MyComputer->Tools|FolderOptions->View(tab)

Down near the bottom of 'Advanced Options' is a box called 'Use Simple File 
Sharing (recommended)'. Turn this OFF. Now, if you look at the Properties 
for any file or folder, there will be an extra Security tab available, and 
you can view and edit the ACLs through this tab.

The notes I saw said that WinXP Home does not allow simple sharing to be 
turned off. I can only assume this is true.

If you try to do programmatic access to the ACLs, you are in for some pain. 
Here are some hints: you can't get to the ACLs at all unless you elevate 
your process token privileges to include SE_SECURITY_NAME - this is 
somewhat analogous to doing setuid() on Unix, except that under Windows, 
even root doesn't have root privileges. As is typical, the documentation 
leads you through this backwards and upside-down, using calls that don't 
work quite as advertised. Here's a sequence that works:

// You need the process token (not the thread token) to adjust its privileges
if (! OpenProcessToken(GetCurrentProcess(), 
TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token))
	<error from GetLastError()>

// You need a magic number corresponding to the SE_SECURITY_NAME text
if (! LookupPrivilegeValue(NULL, SE_SECURITY_NAME, &tkp.Privileges[0].Luid))
	<error from GetLastError()>

// Elevate privileges
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(token, FALSE, &tkp, 0, NULL, NULL);
if ((error = GetLastError()) != 0)
	<error>

// NOW you can get the UID, GID, and DACLs for the file
// I never saw any SACLs on my system, they don't seem relevant to this
// You have to LocalFree(pudesc) when you are done, but not the other pointers
if ((error = GetNamedSecurityInfo(fnam, SE_FILE_OBJECT,
		OWNER_SECURITY_INFORMATION|
		GROUP_SECURITY_INFORMATION|
		DACL_SECURITY_INFORMATION,
		&pusid, &pgsid, &pdacl, NULL, &pudesc)) != 0)
	<error>

// Go back to normal privileges
tkp.Privileges[0].Attributes = 0;
AdjustTokenPrivileges(token, FALSE, &tkp, 0, NULL, NULL);

// Finally, extract the array of ACLs
// You have to LocalFree(pEntries) when you are done
if ((error = GetExplicitEntriesFromAcl(pdacl,&count, &pentries)) != 0)
	<error>

// pusid points to the file owner
// pgsid points to the file group
// pentries[] is an array of 'count' ACL entries for the file

In particular, notice the hodgepodge of different error reporting schemes, 
and the fact that sometimes 0 means success, sometimes it means failure, 
and in one case, means pretty much nothing at all. This caught me a couple 
of times while I was trying to muddle through it.


-- Joe 



--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Problem reports:       http://cygwin.com/problems.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/



More information about the Cygwin mailing list