Using Windows security in Cygwin This paragraph discusses how the Windows security model is utilized in Cygwin to implement POSIX-like permissions, as well as how the authentication model is used to allow to switch the user context in a POSIX-like fashion. The setting of POSIX like file and directory permissions is controlled by the mount option (no)acl which is set to acl by default. We start with a short overview. Note that this overview must be necessarily short. If you want to learn more about the Windows security model, see the Access Control article in MSDN documentation. The POSIX security model is not discussed here, but assumed to be understood by the reader. If you don't know the POSIX security model, search the web for beginner documentation. Overview In the Windows security model, almost any "object" is securable. "Objects" are files, processes, threads, semaphores, etc. Every object has a data structure attached, called a "security descriptor" (SD). The SD contains all information necessary to control who can how access an object. The SD of an object consists of five parts: Flags which control several aspects of this SD. This is not discussed here. The SID of the object owner. The SID of the object owner group. A list of "Access Control Entries" (ACE), called the "Discretionary Access Control List" (DACL). Another list of ACEs, called the "Security Access Control List" (SACL), which doesn't matter for our purpose. We ignore it here. Every ACE contains a so-called "Security IDentifier" (SID) and other stuff which is explained a bit later. Let's talk about the SID first. A SID is a unique identifier for users, groups, computers and AD domains. SIDs are basically comparable to POSIX UIDs and GIDs, but are more complicated because they are unique across multiple machines or domains. A SID is a structure of multiple numerical values. There's a convenient convention to type SIDs. Here's an example: SID of a machine "foo": S-1-5-21-165875785-1005667432-441284377 SID of a user "johndoe" of the system "foo": S-1-5-21-165875785-1005667432-441284377-1023 The leading "S" has no further meaning except to show that this is a SID. The next number is a version number which is always 1 so far. The next two numbers are the authority which shows the initiated what kind of SID this is. There are a couple of builtin accounts and accounts with very special meaning. However, computer and domain SIDs always start with "S-1-5-21". The next three numbers, all 32 bit values, are the unique 96 bit identifier of the comupter system. This is hopefully unique all over the world, but in practice it's sufficient if the comuter SIDs are unique within a single Windows network. As you can see in the above example, SIDs of users (and groups) are identical to the computer SID, except for an additional part, the so-called "relative identifier" (RID). So the SID of a user is always uniquely attached to the system on which the account has been generated. It's a bit different in domains. The domain has its own SID, and that SID is identical to the SID of the first domain controller, on which the domain is created. Domain user SIDs look exactly like the computer user SIDs, the leading part is just the domain SID and the RID is created when the user is created. Ok, consider you created a new domain "bar" on some new domain controller and you would like to create a domain account "johndoe": SID of a domain "bar.local": S-1-5-21-186985262-1144665072-740312968 SID of a user "johndoe" in the domain "bar.local": S-1-5-21-186985262-1144665072-740312968-1207 Ok, so you now have two accounts called johndoe, one account created on the machine "foo", one created in the domain "bar.local". Both have different SIDs and not even the RID is the same. How do the systems know it's the same account? After all, the name is the same, right? The answer is, these accounts are NOT identical. For all the machines know there are two different accounts, one is "FOO\johndoe", the other one is "BAR\johndoe" or "johndoe@bar.local". Different SID, different account. Full stop. The last part of the SID, the so called "Relative IDentifier" (RID), is by default used as UID and/or GID under Cygwin when you create the /etc/passwd and /etc/group files using the mkpasswd and mkgroup tools. Domain account UIDs and GIDs are offset by 10000 by default which might be a bit low for very big organizations. Fortunately there's an option in both tools to change the offset... Do you still remember the SIDs with special meaning? In offical notation they are called "well-known SIDs". For example, POSIX has no GID for the group of "all users" or "world" or "others". The last three rwx bits in a permission value just represent the permissions for "everyone who is not the owner or is member of the owning group". Windows has a SID for these poor souls, the "Everyone" SID. Other well-known SIDs represent more circumstances instead of actual users or groups. Here are a few examples for well-known SIDs: Everyone S-1-1-0 Simply everyone... Batch S-1-5-3 Processes started via the task scheduler are member of this group. Interactive S-1-5-4 Only processes of users which are logged in via an interactive session are members here. Authenticated Users S-1-5-11 Users which have gone through the authentication process and survived. Anonymously accessing users are not incuded here. SYSTEM S-1-5-18 A special account which has all kinds of dangerous rights, sort of an uber-root account. For a full list please refer to Well-known SIDs. Naturally well-known SIDs are the same on each machine, so they are not unique to a machine or domain. They have the same meaning across the Windows network. Additionally there are a couple of well-known builtin groups, which have the same SID on every machine and which have certain user rights by default: administrators S-1-5-32-544 users S-1-5-32-545 guests S-1-5-32-546 ... For instance, every account is usually member in the "Users" group. All administrator accounts are member of the "Administrators" group. That's all about it as far as single machines are involved. In a domain environment it's a bit more tricky. Since these SIDs are not unique to a machine, every domain user and every domain group can be a member of these well known groups. Consider the domain group "Domain Admins". This group is by default in the "Administrators" group. Let's assume the above computer called "foo" is a member machine of the domain "bar.local". If you stick the user "BAR\johndoe" into the group "Domain Admins", this guy will automatically be a mamber of the administrators group on "foo", when logging in on "foo". Neat, isn't it? Back to ACE and ACL. POSIX is able to create three different permissions, the permissions for the owner, for the group and for the world. In contrast the Windows ACL has a potentially infinite number of members... as long as they fit into 64K. Every member is an ACE. ACE consist of three parts: The type of the ACE (allow ACE or deny ACE). Permission bits, 32 of them. The SID for which the permissions are allowed or denied. The two (for us) important types of ACEs are the "access allowed ACE" and the "access denied ACE". As the names imply, the allow ACE tells the system to allow the given permissions to the SID, the deny ACE results in denying the specific permission bits. The possible permissions on objects are more detailed than in POSIX. For example, the permission to delete an object is different from the permission to change object data, and even changing object data can be separated into different permission bits for different kind of data. But there's a problem with the definition of a "correct" ACL which disallows to map certain POSIX permissions cleanly. See . File permissions On NTFS and if the noacl mount option is not specified for a mount point, Cygwin sets file permissions as in POSIX. Basically this is done by defining a SD with the matching owner and group SIDs, and a DACL which contains ACEs for the owner, the group and for "Everyone", which represents what POSIX calls "others". To use Windows security correctly, Cygwin depends on the files /etc/passwd and /etc/group. These files define the traslation between the Cygwin uid/gid and the Windows SID. The SID is stored in the pw_gecos field in /etc/passwd, and in the gr_passwd field in /etc/group. Since the pw_gecos field can contain more information than just a SID, there are some rules for the layout. It's required that the SID is the last entry of the pw_gecos field, assuming that the entries in pw_gecos are comma-separated. The commands mkpasswd and mkgroup usually do this for you. Another interesting entry in the pw_gecos field (which is also usually created by running mkpasswd) is the Windows user name entry. It takes the form "U-domain\username" and is typically used by services to authenticate a user. Logging in through ssh or telnet are two typical scenarios. A typical snippet from /etc/passwd: /etc/passwd: SYSTEM:*:18:544:,S-1-5-18:: Administrators:*:544:544:,S-1-5-32-544:: Administrator:unused:500:513:U-FOO\Administrator,S-1-5-21-790525478-115176313-839522115-500:/home/Administrator:/bin/bash corinna:unused:11001:11125:U-BAR\corinna,S-1-5-21-2913048732-1697188782-3448811101-1001:/home/corinna:/bin/tcsh The SYSTEM entry is usually needed by services. The Administrators entry (Huh? A group in /etc/passwd?) is only here to allow ls to print some file ownerships correctly. Windows doesn't care if the owner of a file is a user or a group. In older versions of Windows NT the default ownership for files created by an administrator account was set to the group Administrators instead of to the creating user account. This has changed, but for those older systems it's convenient to have the Administrators group in /etc/passwd. The really interesting entries are the next two. The Administrator entry is for the local administrator, the corinna entry matches the corinna account in the domain BAR. The information given in the pw_gecos field are all we need to exactly identify an account, and to have a two way translation, from Windows account name/SID to Cygwin account name uid and vice versa. Having this complete information allows us to choose a Cygwin name and uid which doesn't have to match the Windows account at all. As long as the pw_gecos information is available, we're on the safe side: /etc/passwd, tweaked: root:unused:0:513:U-FOO\Administrator,S-1-5-21-790525478-115176313-839522115-500:/home/Administrator:/bin/bash thursday_next:unused:11001:11125:U-BAR\corinna,S-1-5-21-2913048732-1697188782-3448811101-1001:/home/corinna:/bin/tcsh The above /etc/passwd will still work fine. You can now login via ssh as the user "root", and Cygwin dutyfully translates "root" into the Windows user "FOO\Administrators" and files owned by FOO\Administrators are shown to have the uid 0 when calling ls -ln. All you do you're actually doing as Administrator. Files created as root will be owned by FOO\Administrator. And the domain user BAR\corinna can now happily pretend to be Thursday Next, but will wake up sooner or later finding out she's still actually the domain user BAR\corinna... Do I have to mention that you can also rename groups in /etc/group? As long as the SID is present and correct, all is well. This allows for instance to rename the "Administrators" group to "root" as well: /etc/group, tweaked: root:S-1-5-32-544:544: Last but not least you can also change the primary group of a user in /etc/passwd. The only requirement is that the user is actually a member of the new primary group in Windows. For instance, normal users in a domain environment are members in the group "Domain Users", which in turn is member of the well-known group "Users". So, if it's more feasible in your environment that the user's primary group is "Users", just set the user's primary group in /etc/passwd to the Cygwin uid of "Users" (see in /etc/group, default 545). However, here's a WARNING: If you want to do similar changes to your files, please do that only if you're feeling comfortably with the concepts. Otherwise don't be surprised if some stuff doesn't work anymore. If you screwed up things, revert to /etc/passwd and /etc/group files created by mkpasswd and mkgroup. Especially don't change the UID or the name of user SYSTEM. Even if that works mostly, some Cygwin applications running as local service under that account could suddenly start behaving strangely. The POSIX permission mapping leak As promised earlier, here's the problem when trying to map the POSIX permission model on the Windows permission model. There's a leak in the definition of a "correct" ACL which disallows a certain POSIX permission setting. The official documentation explains in short the following: The requested permissions are checked against all ACEs of the user as well as all groups the user is member of. The permissions given in these user and groups access allowed ACEs are accumulated and the resulting set is the set of permissions of that user. The order of ACEs is important. The system reads them in sequence until either any single requested permission is denied or all requested permissions are granted. Reading stops when this condition is met. Later ACEs are not taken into account. All access denied ACEs _should_ precede any access allowed ACE. ACLs following this rule are called "canonical" Note that the last rule is a preference or a definition of correctness. It's not an absolute requirement. All Windows kernels will correctly deal with the ACL regardless of the order of allow and deny ACEs. The second rule is not modified to get the ACEs in the preferred order. Unfortunately the security tab in the file properties dialog of the Windows NT4 explorer is completely unable to deal with access denied ACEs while the Windows 2000 and later properties dialog rearranges the order of the ACEs to canonical order before you can read them. Thank God, the sort order remains unchanged if one presses the Cancel button. But don't even _think_ of pressing OK... Canonical ACLs are unable to reflect each possible combination of POSIX permissions. Example: rw-r-xrw- Ok, so here's the first try to create a matching ACL, assuming the Windows permissions only have three bits, as their POSIX pendants: UserAllow: 110 GroupAllow: 101 OthersAllow: 110 Hmm, because of the accumulation of allow rights the user may execute because the group may execute. Second try: UserDeny: 001 GroupAllow: 101 OthersAllow: 110 Now the user may read and write but not execute. Better? No! Unfortunately the group may write now because others may write. Third try: UserDeny: 001 GroupDeny: 010 GroupAllow: 001 OthersAllow: 110 Now the group may not write as intended but unfortunately the user may not write anymore, either. How should this problem be solved? According to the canonical order a UserAllow has to follow the GroupDeny but it's easy to see that this can never be solved that way. The only chance: UserDeny: 001 UserAllow: 010 GroupDeny: 010 GroupAllow: 001 OthersAllow: 110 Again: This works on all existing versions of Windows NT, at the time of writing from at least NT4 up to Server 2008. Only the GUIs aren't able (or willing) to deal with that order. Switching the user context POSIX applications which have to switch the user context are using the setuid and seteuid calls. Windows doesn't support the concept of these calls in a simple fashion and switching the user context in Windows is generally a tricky process with lots of "behind the scenes" magic involved. Windows uses so-called `access tokens' to identify a user and it's permissions. To switch the user context the application has to request such an `access token'. This is typically done by calling the Win32 API function LogonUser. The access token is returned and either used in ImpersonateLoggedOnUser to change user context of the current process or in CreateProcessAsUser to change user context of a spawned child process. An important restriction is that the application using LogonUser must have special permissions: "Act as part of the operating system" "Replace process level token" "Increase quotas" Note that administrators do not have all these user rights set by default. Two new Cygwin calls are introduced to support porting setuid applications with a minimum of effort. You only give Cygwin the right access token and then you can call seteuid or setuid as usual in POSIX applications. The call to sexec is not needed anymore. Porting a setuid application is illustrated by a short example: #include /* Use the following define to determine the Windows version */ #define is_winnt (GetVersion() < 0x80000000) #endif [...] struct passwd *user_pwd_entry = getpwnam (username); char *cleartext_password = getpass ("Password:"); [...] #ifdef __CYGWIN__ /* Patch the typical password test. */ if (is_winnt) { HANDLE token; /* Try to get the access token from NT. */ token = cygwin_logon_user (user_pwd_entry, cleartext_password); if (token == INVALID_HANDLE_VALUE) error_exit; /* Inform Cygwin about the new impersonation token. Cygwin is able now, to switch to that user context by setuid or seteuid calls. */ cygwin_set_impersonation_token (token); } else #endif /* CYGWIN */ /* Use standard method for W9X as well. */ hashed_password = crypt (cleartext_password, salt); if (!user_pwd_entry || strcmp (hashed_password, user_pwd_entry->pw_password)) error_exit; [...] /* Everything else remains the same! */ setegid (user_pwd_entry->pw_gid); seteuid (user_pwd_entry->pw_uid); execl ("/bin/sh", ...); ]]> The new Cygwin call to retrieve an access token is defined as follows: #include <windows.h> #include <sys/cygwin.h> HANDLE cygwin_logon_user (struct passwd *pw, const char *cleartext_password) You can call that function as often as you want for different user logons and remember the access tokens for further calls to the second function. #include <windows.h> #include <sys/cygwin.h> void cygwin_set_impersonation_token (HANDLE hToken); is the call to inform Cygwin about the user context to which further calls to setuid/seteuid should switch to. While you always need the correct access token to do a setuid/seteuid to another user's context, you are always able to use setuid/seteuid to return to your own user context by giving your own uid as parameter. If you have remembered several access tokens from calls to cygwin_logon_user you can switch to different user contexts by observing the following order: cygwin_set_impersonation_token (user1_token); seteuid (user1_uid); [...] seteuid (own_uid); cygwin_set_impersonation_token (user2_token); seteuid (user2_uid); [...] seteuid (own_uid); cygwin_set_impersonation_token (user1_token); seteuid (user1_uid); etc. Switching User Context Since Cygwin release 1.3.3, applications that are members of the Administrators group and have the Create a token object, Replace a process level token and Increase Quota user rights can switch user context without giving a password by just calling the usual setuid, seteuid, setgid and setegid functions. Up to Windows XP the SYSTEM user has these privileges and can run services such as sshd. However, startung with Windows 2003 SYSTEM lacks the Create a token object right, so it is necessary to create a special user with all the necessary rights, as well as Logon as a service, to run such services. For security reasons this user should be denied the rights to logon interactively or over the network. All this is done by configuration scripts such as ssh-host-config. An important restriction of this method is that a process started without a password cannot access network shares which require authentication. This also applies to subprocesses which switched user context without a password. Therefore, when using ssh or rsh without a password, it is typically not possible to access network drives. Special values of user and group ids If the current user is not present in /etc/passwd, that user's user id is set to a special value of 400. The user name for the current user will always be shown correctly. If another user (or a Windows group, treated as a user) is not present in /etc/passwd, the user id of that user will have a special value of -1 (which would be shown by ls as 65535). The user name shown in this case will be '????????'. If the current user is not present in /etc/passwd, that user's login group id is set to a special value of 401. If another user is not present in /etc/passwd, that user's login group id is set to a special value of -1. If the user is present in /etc/passwd, but that user's group is not in /etc/group and is not the login group of that user, the group id is set to a special value of -1. The name of this group (id -1) will be shown as '????????'. In releases of Cygwin before 1.3.20, the group id 401 had a group name 'None'. Since Cygwin release 1.3.20, the group id 401 is shown as 'mkpasswd', indicating the command that should be run to alleviate the situation. Also, since Cygwin release 1.3.20, if the current user is present in /etc/passwd, but that user's login group is not present in /etc/group, the group name will be shown as 'mkgroup', again indicating the appropriate command. To summarize: If the current user doesn't show up in /etc/passwd, it's group will be named 'mkpasswd'. Otherwise, if the login group of the current user isn't in /etc/group, it will be named 'mkgroup'. Otherwise a group not in /etc/group will be shown as '????????' and a user not in /etc/passwd will be shown as "????????". Note that, since the special user and group names are just indicators, nothing prevents you from actually having a user named `mkpasswd' in /etc/passwd (or a group named `mkgroup' in /etc/group). If you do that, however, be aware of the possible confusion.