Appendix C.  Why is Aegis Set-Uid-Root?

Table of Contents

1. Examples
2. Source Details

The goal for aegis is to have a project that "works". There is a fairly long discussion about this earlier in this User Guide. One of the first things that must be done to ensure that a project is not subject to mystery break downs, is to make sure that the master source of the project cannot be in any way altered in an unauthorized fashion. Note this says "cannot", a stronger statement than "should not".

Aegis is more complicated than, say, set-group-id RCS, because of the flaw with set-group-id: the baseline is writable by the entire development team, so if a developer says "this development process stinks" he can always bypass it, and write the baseline directly. This is a very common source of project disasters. To prevent this, you must have the baseline read-only, and so the set-group-id trick does not work. (The idea here is that there is no way to bypass the QA portions of the process. Sure, set-group-id will prevent accidental edits on the baseline, if the developers are not members of the group, but it does not prevent deliberate checkin of unauthorized code. Again, the emphasis is on "cannot" rather than "should not".)

Also, using the set-group-id trick, you need multiple copies of RCS, one for each project. Aegis can handle many projects, each with a different owner and group, with a single set-uid-root executable.

Aegis has no internal model of security, it uses Unix™ security, and so becomes each user in turn, so Unix™ can determine the permissions.

1. Examples

Here are a few examples of the uid changes in common aegis functions. Unix "permission denied" errors are not shown, but it should be clear where they would occur.

new change (aenc):

become invoking user and read (edit) the change attribute file, validate the attribute file, then become the project owner to write the change state file and the project state file.

develop begin (aedb):

become the project owner and read the project state file and the change state file, to see if the change exists and is available for development, and if the invoking user is on the developer access control list. Become the invoking user, but set the default group to the project group, and make a development directory. Become the project owner again, and update the change state file to say who is developing it and where.

build (aeb):

become the project owner to read the project and change state files, check that the invoking user is the developer of the change, and that the change is in the being developed state. Become the invoking user, but set the default group to the project group, to invoke the build command. Become the project owner to update the change state to remember the build result (the exit status).

copy file into change (aecp):

become the project owner to read the project and change state files. Check that the invoking user is the developer and that the change is in the being developed state, and that the file is not already in the change, and that the file exists in the baseline. Become the invoking user, but set the default group to the project group, and copy the file from the baseline into the development directory. Become the project owner, and update the change state file to remember that the file is included in the change.

integrate fail (aeifail):

become the project owner to read the project and change state files. Check that in invoking user is the integrator of the change, and that the change is in the being integrated state. Become the integrator to collect the integrate fail comments, then become the project owner to delete the integration directory, then become the developer to make the development directory writable again. Then become the project owner to write the change state file, to remember that the change is back in the being developed state.

integrate pass (aeipass):

become the project owner to read the project and change state files. Check that in invoking user is the integrator of the change, and that the change is in the being integrated state. Make the integration directory the new baseline directory and remove the old baseline directory. Write the change and project states to reflect the new baseline and the change is in the completed state. Then become the developer to remove the development directory.

All the mucking about with default groups is to ensure that the reviewers, other members of the same group, have access to the files when it comes time to review the change. The umask is also set (not shown) so that the desired level of "other" access is enforced.

As can be seen, each of the uid change either (a) allows Unix™ to enforce appropriate security, or (b) uses Unix™ security to ensure that unauthorized tampering of project files cannot occur. Each project has an owner and a group: members of the development team obtain read-only access to the project files by membership to the appropriate group, to actually alter project files requires that the development procedure embodied by aegis is carried out. You could have a single account (not a user's account, usually, for obvious conflicts of interest) which owns all project sources, or you could have one account per project. You can have one group per project, if you don't want your various projects to be able to see each other's work, or you could have a single group for all projects.

2. Source Details

For implementation details, see the os_become* functions in the aegis/os.c file. The os_become_init function is called very early in main, in the aegis/main.c file. After that, all accesses are bracketed by os_become and os_become_undo function calls, sometimes indirectly as project_become* or user_become*, etc, functions. You need to actually become each user, because root is not root over NFS, and thus chown tricks do not work, and also because duplicating kernel permission checking in aegis is a little non-portable.

Note, also, that most system calls go via the interface described in the aegis/glue.h file. This isolates the system calls for Unix™ variants which do not have the seteuid function, or do not have a correctly working one. The code in the aegis/glue.c file spawns "proxy" process which uses the setuid function to become the user and stay that way. If the seteuid function is available, it is used instead, making aegis more efficient. This isolation, however, makes it possible for a system administrator to audit the aegis code (for trojans) with some degree of confidence. System calls should be confined to the aegis/log.c aegis/pager.c aegis/os.c and aegis/glue.c files. System calls anywhere else are probably a Bad Thing.