Porting with Interix/SUA: handling changes for crypt() and passwords
This applies to all versions of Interix with Services for Unix (SFU) and Subsystem for Unix-based Applications (SUA) on Microsoft Windows OS systems.
There are many application already written that use the tradition Unix API crypt(3). Programmer compiling this code on Interix will find at link time that crypt() appears to be missing. From a “I just wanted to compile and link” objective this outcome does not appear satisfactory. However, this result is a blessing in disguise as it can save you some potential headaches.
There are two general uses of crypt(): for hiding data and for verifying user passwords.
Hiding data with crypt()
If the application is using crypt() for hiding data you may want to consider updating this part of the application since crypt() is now a rather weak method of securing data. However, if you determine that you must use crypt() there is a simple solution that works in most case. That is to link to the library named crypt (libcrypt). To your link line you will need to add “-lcrypt”. If you need, or want, the function prototype for crypt() then you can add it to a header file for your application or add it to the traditional header file <unistd.h> (it’s missing in SFU 3.0 for some reason).
extern char * __cdecl crypt (const char *, const char *);
Verifying Passwords: from crypt() to setuser()
The majority of applications use the crypt() function for verifying user passwords. These applications tend to assume that there will be a “/etc/passwd” file (or it’s equivalent shadow) that has the password stored from the output of crypt(). There are some applications that assume the encrypted password can be obtained from a getpwnam(2) call and looking at the pw_passwd member of the struct passwd returned.
With Interix the user database is not store in “/etc/passwd” or shadow equivalent. Nor is the encrypted password obtainable with a getpwnam() call – the pw_passwd is always set to “*”. The user password is stored away by the LSA (Local Security Authority). The LSA performs this task for the Win32 subsystem as well.
The purpose of the call to crypt() is to verify that the password a user has provided is valid. Calling crypt() and comparing the result with what was stored in “/etc/passwd” was the way to do it on Unix systems. The matching method to do this with Interix is the API setuser(2).
The setuser() function is declared as:
int setuser(char *username, char *password, int flags)
You can verify a user’s password by passing as the second argument the plaintext string that the user has provided with the first argument being the username and the flags argument being SU_CHECK. A return value of 0 (zero) means that the password provided matches for this user.
Once the setuser() call returns, successful or not, the password string should be wiped out for security purposes. The shorter the time period that a plaintext password, correct or not, is in memory the better.
memset(password, 0, strlen(password))
At this point if the provided password is incorrect, then whatever action would have been taken using crypt() can likely still be done.
If the password is valid there are two situations: the application retains the same ownership or the application wants to change ownership to the user whose password was just verified. If it’s the former (retains) then nothing needs to be adjusted in the general programming sense. There may be something specific to the application you are working with. You should always check to be certain. When it is the latter you will discover API calls to such as setuid(2), setgid(2) and other similar API’s. Interix supports these API’s so your code will link and run. But using setuid() is likely not the best solution. The calls to setuid() et al. are to set the current process into the context of the user. These applications usually make several of these calls and each involves a fair amount of system resources.
The better solution is to call setuser() with the flags argument set to SU_COMPLETE. This is a comprehensive setting of the process into the user’s context and the consumption of system resources is minimized. An additional benefit, from a security viewpoint, is that a process calling setuser() does not need to be a process that is special privileged. A process calling setuid() on the other hand does need to be specially privileged by it already being owned by the user Administrator.
Some efficiency can be obtained by adjusting the source code so that only the call to setuser() with the SU_COMPLETE flag is needed. The single setuser() call allows for the password to be cleared from memory earlier than with two setuser() calls. There are code instances where two calls to setuser() are needed with one being SU_CHECK and the second being SU_COMPLETE. When fork(2) appears in the code this is a good indicator.
It is not unusual that the code may fork(2) to create a child process at this point. The parent process may continue along, wait for the child or both. Daemons often do this. The child process will then make the change to the new context. In such occurrences you will definitely want to have the two separate setuser() calls so that the parent retains it’s identity and the child gains it’s new identity.
It is also common to find a call to one of the API’s in the exec(2) family. If this is the case then you may want to consider using one of the matching API’s from the exec_asuser(2) family. A special structure will need to be filled in prior to the API call, but it can prove to be a bit more efficient than a setuser() followed by an exec(). The end results will not be different whichever method you choose as the same verification and identity creation pathways are followed in the Interix subsystem.
You will want to read the following man pages for more details: setuser(), setuid(), seteuid(), setreuid(), setgid(), setegid(), setregid(), getuid(), getgid(), getpwnam(), getpwuid(), getpass(), memset(), exec(), exec_asuser(), fork()
You may also want to examine the following header files: interix/security.h, sys/types.h, unistd.h, pwd.h, string.h