Skip to main content
p. 189

7.9 User Access Topics

The remainder of this chapter covers several topics on how users get the permission to log in, switch to other users, and perform other related tasks. This is somewhat advanced material, and you’re welcome to skip to the next chapter if you’re ready to get your hands dirty with some process internals.

7.9.1 User IDs and User Switching​

We’ve discussed how setuid programs such as sudo and su allow you to temporarily change users, and we’ve covered system components like login that control user access. Perhaps you’re wondering how these pieces work and what role the kernel plays in user switching.

When you temporarily switch to another user, all you’re really doing is changing your user ID. There are two ways to do this, and the kernel handles both. The first is with a setuid executable, which was covered in Section 2.17. The second is through the setuid() family of system calls. There are a few different versions of this system call to accommodate the various user IDs associated with a process, as you’ll learn in Section 7.9.2.

The kernel has basic rules about what a process can or can’t do, but here are the three essentials that cover setuid executables and setuid():

  • A process can run a setuid executable as long as it has adequate file permissions.
  • A process running as root (user ID 0) can use setuid() to become any other user.
  • A process not running as root has severe restrictions on how it may use setuid(); in most cases, it cannot.

As a consequence of these rules, if you want to switch user IDs from a regular user to another user, you often need a combination of the methods. For example, the sudo executable is setuid root, and once running, it can call setuid() to become another user.

note

At its core, user switching has nothing to do with passwords or usernames. Those are strictly user-space concepts, as you first saw in the /etc/passwd file in Section 7.3.1. You’ll learn more details about how this works in Section 7.9.4.

7.9.2 Process Ownership, Effective UID, Real UID, and Saved UID​

Our discussion of user IDs so far has been simplified. In reality, every process has more than one user ID. So far, you are familiar with the effective user ID (effective UID, or euid), which defines the access rights for a process (most significantly, file permissions). A second user ID, the real user ID (real UID, or ruid), indicates who initiated a process. Normally, these IDs are identical, but when you run a setuid program, Linux sets the euid to the program’s owner during execution, but it keeps your original user ID in the ruid.

p. 190

The difference between the effective and real UIDs is confusing, so much so that a lot of documentation regarding process ownership is incorrect.

Think of the euid as the actor and the ruid as the owner. The ruid defines the user that can interact with the running process—most significantly, which user can kill and send signals to a process. For example, if user A starts a new process that runs as user B (based on setuid permissions), user A still owns the process and can kill it.

We’ve seen that most processes have the same euid and ruid. As a result, the default output for ps and other system diagnostic programs show only the euid. To view both user IDs on your system, try this, but don’t be surprised if you find that the two user ID columns are identical for all processes on your system:

$ ps -eo pid,euser,ruser,comm

To create an exception just so that you can see different values in the columns, try experimenting by creating a setuid copy of the sleep command, running the copy for a few seconds, and then running the preceding ps command in another window before the copy terminates.

To add to the confusion, in addition to the real and effective user IDs, there’s also a saved user ID (which is usually not abbreviated). A process can switch its euid to the ruid or saved user ID during execution. (To make things even more complicated, Linux has yet another user ID: the file system user ID, or fsuid, which defines the user accessing the filesystem but is rarely used.)

Typical Setuid Program Behavior​

The idea of the ruid might contradict your previous experience. Why don’t you have to deal with the other user IDs very frequently? For example, after starting a process with sudo, if you want to kill it, you still use sudo; you can’t kill it as your own regular user. Shouldn’t your regular user be the ruid in this case, giving you the correct permissions?

The cause of this behavior is that sudo and many other setuid programs explicitly change the euid and ruid with one of the setuid() system calls. These programs do so because there are often unintended side effects and access problems when all of the user IDs do not match.

note

If you’re interested in the details and rules regarding user ID switching, read the setuid(2) manual page and check the other manual pages listed in the SEE ALSO section. There are many different system calls for diverse situations.

Some programs don’t like to have an ruid of root. To prevent sudo from changing the ruid, add this line to your /etc/sudoers file (and beware of side effects on other programs you want to run as root!):

Defaults stay_setuid
p. 191

Security Implications​

Because the Linux kernel handles all user switches (and as a result, file access permissions) through setuid programs and subsequent system calls, systems developers and administrators must be extremely careful with two things:

  • The number and quality of programs that have setuid permissions
  • What those programs do

If you make a copy of the bash shell that is setuid root, any local user can execute it and have complete run of the system. It’s really that simple. Furthermore, even a special-purpose program that is setuid root can pose a danger if it has bugs. Exploiting weaknesses in programs running as root is a primary method of systems intrusion, and there are too many such exploits to count.

Because there are so many ways to break into a system, preventing intrusion is a multifaceted affair. One of the most essential ways to keep unwanted activity off your system is to enforce user authentication with usernames and good passwords.

7.9.3 User Identification, Authentication, and Authorization​

A multiuser system must provide basic support for user security in three areas: identification, authentication, and authorization. The identification portion of security answers the question of who users are. The authentication piece asks users to prove that they are who they say they are. Finally, authorization is used to define and limit what users are allowed to do.

When it comes to user identification, the Linux kernel knows only the numeric user IDs for process and file ownership. The kernel knows authorization rules for how to run setuid executables and how user IDs may run the setuid() family of system calls to change from one user to another. However, the kernel doesn’t know anything about authentication: usernames, passwords, and so on. Practically everything related to authentication happens in user space.

We discussed the mapping between user IDs and passwords in Section 7.3.1; now we’ll cover how user processes access this mapping. We’ll begin with an oversimplified case, in which a user process wants to know its username (the name corresponding to the euid). On a traditional Unix system, a process could do something like this to get its username:

  1. The process asks the kernel for its euid with the geteuid() system call.
  2. The process opens the /etc/passwd file and starts reading at the beginning.
  3. The process reads a line of the /etc/passwd file. If there’s nothing left to read, the process has failed to find the username.
  4. The process parses the line into fields (breaking out everything between the colons). The third field is the user ID for the current line. p. 192
  5. The process compares the ID from step 4 to the ID from step 1. If they’re identical, the first field in step 4 is the desired username, and the process can stop searching and use this name.
  6. The process moves on to the next line in /etc/passwd and goes back to step 3.

This is a long procedure, and a real-world implementation is usually even more complicated.

7.9.4 Using Libraries for User Information​

If every developer who needed to know the current username had to write all of the code you’ve just seen, the system would be a horrifyingly disjointed, buggy, bloated, and unmaintainable mess. Fortunately, there are often standard libraries we can use to perform repetitive tasks like this; in this case, all you’d normally need to do to get a username is call a function like getpwuid() in the standard library after you have the answer from geteuid(). (See the manual pages for these calls for more on how they work.)

The standard library is shared among the executables on your system, so you can make significant changes to the authentication implementation without changing any program. For example, you can move away from using /etc/passwd for your users and use a network service such as LDAP instead by changing only the system configuration.

This approach has worked well for identifying usernames associated with user IDs, but passwords have proven more troublesome. Section 7.3.1 describes how, traditionally, the encrypted password was part of /etc/passwd, so if you wanted to verify a password that a user entered, you’d encrypt whatever the user typed and compare it to the contents of the /etc/passwd file.

This traditional implementation has many limitations, including:

  • It doesn’t allow you to set a system-wide standard for the encryption protocol.
  • It assumes that you have access to the encrypted password.
  • It assumes that you want to prompt the user for a password every time the user wants to access something that requires authentication (which gets annoying).
  • It assumes that you want to use passwords. If you want to use one-time tokens, smart cards, biometrics, or some other form of user authentication, you have to add that support yourself.

Some of these limitations contributed to the development of the shadow password package discussed in Section 7.3.3, which took the first step in allowing system-wide password configuration. But the solution to the bulk of the problems came with the design and implementation of PAM.