CITS2002 Systems Programming  
CITS2002 CITS2002 schedule  

Users and their Operating System representation

Traditionally, an operating system positions itself directly between running processes and the hardware, and only permits information about the hardware, and software's queries and requests to pass through the kernel.

System calls are (supposed to be) the only mechanism by which processes may interact with the operating system and the resources it is protecting and managing (i.e. a process may not randomly read nor write the memory of the kernel, or of another process, itself).

Unix has adopted the approach that processes are the only active entities on a system (i.e. devices are passive). Processes act on the behalf of users, while accessing resources in a controlled fashion.

If these two approaches have been successful, we can study the basic security aspects of an operating system by examining:

  • the state of processes at runtime,
  • how a process's state may be changed,
  • how a process represents a user,
  • the mechanisms used to protect resources.

CITS2002 Systems Programming, Lecture 22, p1, 15th October 2019.

 

User IDs and system calls

Users are represented within the Unix kernel by 16-bit unsigned user identifiers, or userids (there are recent changes to 32-bits):

Operating systems support integral values for userids in preference to strings for their fixed length and their speed of comparison.

Although there may only be 216 possible values, even modern variants of Unix do not use the userid as an index into any kernel tables. Moreover, no userid is considered invalid.

  • The user-identifier is established when logging into a Unix system. A correct combination of user-name and password when logging in, or the validation of a network-based connection, set the user-identifier (uid) in the process control block of the user's login shell, or command interpreter.

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    
      printf("user-id: %i\n", getuid() );  
    

    Unless modified, this user-identifier is inherited by all processes invoked from the initial login shell. Under certain conditions, the user-identifier may be changed and determined with the system calls setuid() and getuid().

CITS2002 Systems Programming, Lecture 22, p2, 15th October 2019.

 

Effective user IDs

A further property of each process is its effective user identifier:

  • The effective user-identifier is, by default, the same as the user-identifier, but may be temporarily changed to a different value to offer temporary privileges.

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    
      printf("real user-id: %i\n",      getuid() );  
      printf("effective user-id: %i\n", geteuid() );  
    

    The successful invocation of set-user-id programs, such as passwd and login will, typically, set the effective user-identifier for the lifetime of that process.

    Under certain conditions, the effective user-identifier may be changed and determined with the system calls seteuid() and geteuid().

CITS2002 Systems Programming, Lecture 22, p3, 15th October 2019.

 

The Unix kernel does not check userids

With the single exception of userid==0 (discussed later) the Unix kernel places no special significance of any userids.

In particular, because account- and user-management is not treated as an activity to the kernel, the kernel collapses users with the same userid to be a single user.

A frequently seen flaw is one where two (named) users have the same userid in the password file, or a user is incorrectly given a negative userid (such as a user named nobody with a userid of -2) on the assumption that all application software will consistently treat this as a signed or unsigned integer.

User-level tools are required to check the consistency of password files.

CITS2002 Systems Programming, Lecture 22, p4, 15th October 2019.

 

An incorrect use of Unix userids?

Since its inception, Unix has also used distinct userids to represent activities or even programs, rather than 'real' users. Common examples include userids with names such as shutdown, mail, news, etc, typically with small userids.

Processes with these userids do not run with elevated privilege, but typically run in an environment where no human will notice them. These allocated userids do not invoke interactive sessions, (or are not supposed to!) and so no human operator is immediately notified if they do, or if they try unsuccessfully several times.

Care must be taken to ensure that these 'users' do not have passwords, blank passwords, home directories, interactive login shells, etc. Typical approaches are to place an asterisk in their /etc/passwd password field, and set /sbin/nologin as their login shell.

Properties of the Unix Superuser

Unix uses the special userid value of 0 to represent its only special user, the superuser (or root) account.

Processes acting on behalf of the superuser can often access additional resources (often, incorrectly, stated as 'everything') because most system calls responsible for checking user permissions bypass their checks if being invoked with userid==0.

Unix is frequently criticized for both having a concept such as a
superuser, or for encouraging security practices which now rely on it.

It is thus the single greatest target of attack on a Unix system.

CITS2002 Systems Programming, Lecture 22, p5, 15th October 2019.

 

Properties of the Unix Superuser, continued

The result is that there appear to be no files, etc, that cannot be hidden from the superuser using the standard application programs which report and manipulate such resources.

For example, because the superuser can access all files and directories on a local system, it is simpler to use the superuser account/privilege to backup data volumes. It is cumbersome to direct the backup programs to not backup certain items.

Although the superuser has greater access to otherwise protected resources, the Unix kernel will not permit the superuser to undermine the integrity of the operating system itself. For example:

  • although the superuser can create a new file in any directory through a call to the open() or creat() system calls, and that details of this new file are written to the directory by the kernel itself, the superuser cannot open and explicitly write to the directory.

  • the superuser does not have the ability to write a file to a read-only file system, but can unmount the file system, re-mount it with an elevate read-write status, and then write the file.

CITS2002 Systems Programming, Lecture 22, p6, 15th October 2019.

 

Constraining a process's access with chroot

An additional approach to constraining how files and directories may be accessed, is to constrain how a process may access its resources.

Strictly speaking, this is not an alternative to setting file-access permissions, but may be used in combination with the previous techniques.

On behalf of each process, the kernel manages a current working directory, relative to which all file accesses are made, and a root directory 'above' which the process may not change directory, or open files.

The chroot system call changes the root directory of a calling process to be that specified by the function's argument.

This directory will be used for path names beginning with / and constrains traversal with ../../.. etc. The root directory attribute is inherited by all children of the current process.

Under normal execution, a process wishing to constrain the access of a subprocess would:

  • fork() a new (child) subprocess,
  • set the new root directory of the child, using chroot(),
  • execl() the required process.

Traditionally, only the super-user may change the root directory of the current process, and so the chroot application program exists with its setuid bit set to temporarily run as the superuser while making the system call.

CITS2002 Systems Programming, Lecture 22, p7, 15th October 2019.

 

Supporting Groups of Users

As well as the single userid of each of its process, each user is also represented by one of more group identifiers, or groupids.

groupids are similarly represented as 16-bit unsigned integral values. Each process maintains the primary groupid of its user (owner). The primary groupid for each user is initially located from the /etc/passwd file, typically when a user logs in.

chris:9HsKSemoJlxpA:333:110:Chris McDonald:/Users/chris:/bin/zsh

  • Under variants of Unix derived from the Berkeley (BSD) stream of Unix, each process also carries an array of 32 additional groupids, representing additional groups that the user is in. These are located from the /etc/group file, using the user-level library function initgroups(), and set with the system call setgroups().

  • Variants of Unix derived from the AT&T System-V stream, support the newgrp command, which is similar to login in many respects but permits a process's primary group to be changed.

    Additionally, the newgrp command permits an interactive process to change its primary group if it can provide the group's password (maintained in the /etc/group file).

Unix groups are used to constrain access to resources, such as files and some interprocess communication channels, to processes that must hold the required primary groupid, or (if BSD) hold it in its vector of groupids.

CITS2002 Systems Programming, Lecture 22, p8, 15th October 2019.

 

Supporting Groups of Users, continued

We recall that operating system kernels mostly deal with integer values, and not strings (file pathnames being an obvious exception).

Thus, while the operating system kernel will maintain group information (integers and arrays of integers) about users and their executing processes, additional information about these integers requires library functions (outside of the kernel).

For example, getgroups() is a system-call (with its documentation in section-2 of the online manual) to request all group-IDs of which the calling user-ID is a member.

We can then use the library function getgrgid() (with its documentation in section-3 of the online manual) to find information about each group-ID.

#include <stdio.h>
#include <unistd.h>
#include <grp.h>

#define MY_MAXGROUPS   100

void print_my_groups(void)
{
    gid_t groups[MY_MAXGROUPS];

    int ngroups = getgroups(MY_MAXGROUPS, groups);

    for(int g=0 ; g<ngroups ; ++g) {
        struct group *gp    = getgrgid(groups[g]);

        if(gp != NULL) {
            printf("gid: %-8i name: %s\n", groups[g], gp->gr_name);

            printf("\tmembers:");
            for(int m=0 ; gp->gr_mem[m] != NULL ; ++m) {
                printf(" %s", gp->gr_mem[m]);
            }
            printf("\n\n");
        }
    }
}

CITS2002 Systems Programming, Lecture 22, p9, 15th October 2019.

 

Changing and Setting User Information

As userids are used extensively to control and constrain a process's execution, and to provide possible auditing/logging of a user's activities, it is not possible for a non-superuser process to change its own userid.

If this were possible, an attacker could assume permissions of anyone!

However, there are occasions where a process must undertake an activity or temporarily have access to resources that it would not otherwise have. Examples include the passwd and newgrp programs already described. Each must have access to files containing passwords (possibly shadowed versions of these), or require the ability to demonstrate that the activity is permitted.

In 1973, Dennis Ritchie, one of the original two designers of Unix filed a patent for the Unix set-uid mechanism:

US Patent 4,135,240: An improved arrangement for controlling access to data files by computer users. Access permission bits are used in the prior art to separately indicate permissions for the file owner and nonowners to read, write and execute the file contents. An additional access control bit is added to each executable file. When this bit is set to one, the identification of the current user is changed to that of the owner of the executable file. The program in the executable file then has access to all data files owned by the same owner. This change is temporary, the proper identification being restored when the program is terminated.

In essence, program images are stored in files, and each file has an owner. Invoking a process, from a program file having its setuid bit set results in the (new) process executing with the privileges of the file's owner.

CITS2002 Systems Programming, Lecture 22, p10, 15th October 2019.

 

Changing and Setting User Information, continued

This gives us the ability for a process to run with another userid, of course provided that the 'owner' of that userid has established the appropriate permissions for the program's file.

    chmod  4711  myprogram

           or

    chmod  u+s,a+x  myprogram

In effect, the owner of the (different) permissions is not explicitly changing the userid of a running process, but is permitting a (new) process to run with their permissions. Examples for its use include letting another user open a specific data file only by using your setuid program.

This facility takes on special significance when the owner of the program's file is the superuser -
any files then run as 'setuid root' run with the elevated permissions assigned to the superuser. (Hopefully) in all cases these are necessary:

    -rws--x--x  1 root root  4764 Apr  2 07:26 /usr/bin/newgrp
    -r-s--x--x  1 root root 15104 Mar 14 09:44 /usr/bin/passwd

setuid executable files, particularly if owned by root, are particular points of potential vulnerability on a Unix system, and regular checks with programs such as find should be made to locate (and justify) their existence.

A similarly extended execution state may be used with set-groupid permission mode bits on program files. Processes may execute as if the user running them were in the additional Unix group. This may provide access to resources that were otherwise constrained to the particular group.

CITS2002 Systems Programming, Lecture 22, p11, 15th October 2019.