exec Functions
When a process calls one of the exec functions, that process is completely replaced by the new program, and the new program starts executing at its main function. The process ID does not change across an exec, because a new process is not created; exec merely replaces the current process — its text, data, heap, and stack segments — with a brand-new program from disk.
There are seven different exec functions, but we’ll often simply refer to ‘‘the exec function,’’ which means that we could use any of the seven functions. These seven functions round out the UNIX System process control primitives. With fork, we can create new processes; and with the exec functions, we can initiate new programs. The exit function and the wait functions handle termination and waiting for termination.
#include <unistd.h>
int execl(const char *pathname, const char *arg0, ...);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ...
/* (char *)0, char *const envp[] */ );
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
The first difference in these functions is that the first four take a pathname argument, the next two take a filename argument, and the last one takes a file descriptor argument. When a filename argument is specified,
- If filename contains a slash, it is taken as a pathname.
- Otherwise, the executable file is searched for in the directories specified by the PATH environment variable.
The PATH variable contains a list of directories, called path prefixes, that are separated by colons.
Historically, the limit in older System V implementations was 5,120 bytes. Older BSD systems had a limit of 20,480 bytes. The limit in current systems is much higher.
We’ve mentioned that the process ID does not change after an exec, but the new
program inherits additional properties from the calling process:
- Process ID and parent process ID
- Real user ID and real group ID
- Supplementary group IDs
- Process group ID
- Session ID
- Controlling terminal
- Time left until alarm clock
- Current working directory
- Root directory
- File mode creation mask
- File locks
- Process signal mask
- Pending signals
- Resource limits
- Nice value (on XSI-conformant systems)
- Values for tms_utime, tms_stime, tms_cutime, and tms_cstime
POSIX.1 specifically requires that open directory streams (recall the opendir function from Section 4.22) be closed across an exec. This is normally done by the opendir function calling fcntl to set the close-on-exec flag for the descriptor corresponding to the open directory stream.
In many UNIX system implementations, only one of these seven functions, execve, is a system call within the kernel. The other six are just library functions that eventually invoke this system call.
The fexecve library function uses /proc to convert the file descriptor argument into a pathname that can be used by execve to execute the program.
This describes how fexecve is implemented in FreeBSD 8.0 and Linux 3.2.0. Other systems might take a different approach. For example, a system without /proc or /dev/fd could implement fexecve as a system call veneer that translates the file descriptor argument into an i-node pointer, implement execve as a system call veneer that translates the pathname argument into an i-node pointer, and place all the rest of the exec code common to both execve and fexecve in a separate function to be called with an i-node pointer for the file to be executed.
#include "apue.h"
#include <sys/wait.h>
char *env_init[]={"USER=unknown", "PATH=/tmp",NULL};
int main(void)
{
pid_t pid;
TELL_WAIT();
if((pid=fork())<0)
{
err_sys("fork error");
}
else if(pid==0)
{
if(execle("/home/sar/bin/echoall","echoall","myarg1","MY ARG2", (char*)0,env_init)<0)
err_sys("execle error");
}
if(waitpid(pid,NULL,0)<0)
err_sys("wait error");
if((pid==fork())<0)
{
err_sys("fork error");
}
else if(pid==0)
{
if(execlp("echoall","echoall","only 1 arg",(char *)0)<0)
err_sys("execlp error");
}
if(waitpid(pid,NULL,0)<0)
{
err_sys("wait error");
}
if((pid=fork())<0)
{
err_sys("fork_error");
}
else if(pid==0)
{
if(execlp("echoall","echoall","only 1 arg",(char *)0)<0)
err_sys("execlp error");
}
return 0;
}
Example of exec functions
#include "apue.h"
int main(int argc, char *argv[])
{
int i;
char **ptr;
extern char **environ;
for(i=0;i<argc;i++)
printf("argv[%d]: %d\n",i,argv[i]);
for(ptr=environ;*ptr!=0;ptr++)
printf("%d\n",*ptr);
return 0;
}
Echo all command-line arguments and all environment strings
Changing User IDs and Group IDs
In the UNIX System, privileges, such as being able to change the system’s notion of the current date, and access control, such as being able to read or write a particular file, are based on user and group IDs. When our programs need additional privileges or need to gain access to resources that they currently aren’t allowed to access, they need to change their user or group ID to an ID that has the appropriate privilege or access.
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
There are rules for who can change the IDs. Let’s consider only the user ID for now. (Everything we describe for the user ID also applies to the group ID.)
1.If the process has superuser privileges, the setuid function sets the real user ID, effective user ID, and saved set-user-ID to uid.
2.If the process does not have superuser privileges, but uid equals either the real user ID or the saved set-user-ID, setuid sets only the effective user ID to uid.
The real user ID and the saved set-user-ID are not changed.
3.If neither of these two conditions is true, errno is set to EPERM and −1 is returned.
Here, we are assuming that _POSIX_SAVED_IDS is true. If this feature isn’t provided, then delete all preceding references to the saved set-user-ID.
We can make a few statements about the three user IDs that the kernel maintains.
1.Only a superuser process can change the real user ID. Normally, the real user ID is set by the login(1) program when we log in and never changes. Because login is a superuser process, it sets all three user IDs when it calls setuid.
2.The effective user ID is set by the exec functions only if the set-user-ID bit is set for the program file. If the set-user-ID bit is not set, the exec functions leave the effective user ID as its current value. We can call setuid at any time to set the effective user ID to either the real user ID or the saved set-user-ID. Naturally, we can’t set the effective user ID to any random value.
3.The saved set-user-ID is copied from the effective user ID by exec. If the file’s set-user-ID bit is set, this copy is saved after exec stores the effective user ID from the file’s user ID.
FreeBSD 8.0 and LINUX 3.2.0 provide the getresuid and getresgid functions, which can be used to get the saved set-user-ID and saved set-group-ID, respectively.
#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);
Both setreuid and setregid are included in the XSI option in POSIX.1. As such, all UNIX System implementations are expected to provide support for them.
#include <unistd.h>
int seteuid(uid_t uid);
int setegid(gid_t gid);
An unprivileged user can set its effective user ID to either its real user ID or its saved set-user-ID. For a privileged user, only the effective user ID is set to uid. (This behavior differs from that of the setuid function, which changes all three user IDs.)
On Linux 3.2.0, the at program is installed set-user-ID to user daemon. On FreeBSD 8.0, Mac OS X 10.6.8, and Solaris 10, the at program is installed set-user-ID to user root. This allows the at command to write privileged files owned by the daemon that will run the commands on behalf of the user running the at command.
On Linux 3.2.0, the programs are run by the atd(8) daemon. On FreeBSD 8.0 and Solaris 10, the programs are run by the cron(1M) daemon. On Mac OS X 10.6.8, the programs are run by the launchd(8) daemon.
The following steps take place.
1.Assuming that the at program file is owned by root and has its set-user-ID bit
set, when we run it, we have
real user ID = our user ID (unchanged)
effective user ID = root
saved set-user-ID = root
2.The first thing the at command does is reduce its privileges so that it runs with
our privileges. It calls the seteuid function to set the effective user ID to our
real user ID. After this, we have
real user ID = our user ID (unchanged)
effective user ID = our user ID
saved set-user-ID = root (unchanged)
3.The at program runs with our privileges until it needs to access the configuration files that control which commands are to be run and the time at which they need to run. These files are owned by the daemon that will run the commands for us. The at command calls seteuid to set the effective user ID to root. This call is allowed because the argument to seteuid equals the saved set-user-ID. (This is why we need the saved set-user-ID.) After this, we have
real user ID = our user ID (unchanged)
effective user ID = root
saved set-user-ID = root (unchanged)
Because the effective user ID is root, file access is allowed.
4.After the files are modified to record the commands to be run and the time at which they are to be run, the at command lowers its privileges by calling seteuid to set its effective user ID to our user ID. This prevents any accidental misuse of privilege. At this point, we have
real user ID = our user ID (unchanged)
effective user ID = our user ID
saved set-user-ID = root (unchanged)
5.The daemon starts out running with root privileges. To run commands on our
behalf, the daemon calls fork and the child calls setuid to change its user ID
to our user ID. Because the child is running with root privileges, this changes
all of the IDs. We have
real user ID = our user ID
effective user ID = our user ID
saved set-user-ID = our user ID
Now the daemon can safely execute commands on our behalf, because it can access only the files to which we normally have access. We have no additional permissions.