The Linux-PAM Module Writers' Guide: Programming notes

Previous Next Table of Contents

## 5. Programming notes

Here we collect some pointers for the module writer to bear in mind when writing/developing a Linux-PAM compatible module.

## 5.1 Security issues for module creation

### Sufficient resources

Care should be taken to ensure that the proper execution of a module is not compromised by a lack of system resources. If a module is unable to open sufficient files to perform its task, it should fail gracefully, or request additional resources. Specifically, the quantities manipulated by the `setrlimit(2)` family of commands should be taken into consideration.

### Who's who?

Generally, the module may wish to establish the identity of the user requesting a service. This may not be the same as the username returned by `pam_get_user()`. Indeed, that is only going to be the name of the user under whose identity the service will be given. This is not necessarily the user that requests the service.

In other words, user X runs a program that is setuid-Y, it grants the user to have the permissions of Z. A specific example of this sort of service request is the su program: user `joe` executes su to become the user jane. In this situation X=`joe`, Y=`root` and Z=`jane`. Clearly, it is important that the module does not confuse these different users and grant an inappropriate level of privilege.

The following is the convention to be adhered to when juggling user-identities.

- X, the identity of the user invoking the service request. This is the user identifier; returned by the function `getuid(2)`.
- Y, the privileged identity of the application used to grant the requested service. This is the effective user identifier; returned by the function `geteuid(2)`.
- Z, the user under whose identity the service will be granted. This is the username returned by `pam_get_user(2)` and also stored in the Linux-PAM item, `PAM_USER`.
- Linux-PAM has a place for an additional user identity that a module may care to make use of. This is the `PAM_RUSER` item. Generally, network sensitive modules/applications may wish to set/read this item to establish the identity of the user requesting a service from a remote location.

Note, if a module wishes to modify the identity of either the `uid` or `euid` of the running process, it should take care to restore the original values prior to returning control to the Linux-PAM library.

### Using the conversation function

Prior to calling the conversation function, the module should reset the contents of the pointer that will return the applications response. This is a good idea since the application may fail to fill the pointer and the module should be in a position to notice!

The module should be prepared for a failure from the conversation. The generic error would be `PAM_CONV_ERR`, but anything other than `PAM_SUCCESS` should be treated as indicating failure.

### Authentication tokens

To ensure that the authentication tokens are not left lying around the items, `PAM_AUTHTOK` and `PAM_OLDAUTHTOK`, are not available to the application: they are defined in `<security/pam_modules.h>`. This is ostensibly for security reasons, but a maliciously programmed application will always have access to all memory of the process, so it is only superficially enforced. As a general rule the module should overwrite authentication tokens as soon as they are no longer needed. Especially before `free()`'ing them. The Linux-PAM library is required to do this when either of these authentication token items are (re)set.

Not to dwell too little on this concern; should the module store the authentication tokens either as (automatic) function variables or using `pam_[gs]et_data()` the associated memory should be over-written explicitly before it is released. In the case of the latter storage mechanism, the associated `cleanup()` function should explicitly overwrite the `*data` before `free()`'ing it: for example,

> `

>     /*
>      * An example cleanup() function for releasing memory that was used to
>      * store a password.
>      */

>     int cleanup(pam_handle_t *pamh, void *data, int error_status)
>     {
>         char *xx;

>         if ((xx = data)) {
>             while (*xx)
>                 *xx++ = '\0';
>             free(data);
>         }
>         return PAM_SUCCESS;
>     }

> `

## 5.2 Use of `syslog(3)`

Only rarely should error information be directed to the user. Usually, this is to be limited to ``sorry you cannot login now'' type messages. Information concerning errors in the configuration file, `/etc/pam.conf`, or due to some system failure encountered by the module, should be written to `syslog(3)` with facility-type `LOG_AUTHPRIV`.

With a few exceptions, the level of logging is, at the discretion of the module developer. Here is the recommended usage of different logging levels:

- As a general rule, errors encountered by a module should be logged at the `LOG_ERR` level. However, information regarding an unrecognized argument, passed to a module from an entry in the `/etc/pam.conf` file, is required to be logged at the `LOG_ERR` level.
- Debugging information, as activated by the `debug` argument to the module in `/etc/pam.conf`, should be logged at the `LOG_DEBUG` level.
- If a module discovers that its personal configuration file or some system file it uses for information is corrupted or somehow unusable, it should indicate this by logging messages at level, `LOG_ALERT`.
- Shortages of system resources, such as a failure to manipulate a file or `malloc()` failures should be logged at level `LOG_CRIT`.
- Authentication failures, associated with an incorrectly typed password should be logged at level, `LOG_NOTICE`.

## 5.3 Modules that require system libraries

Writing a module is much like writing an application. You have to provide the "conventional hooks" for it to work correctly, like `pam_sm_authenticate()` etc., which would correspond to the `main()` function in a normal function.

Typically, the author may want to link against some standard system libraries. As when one compiles a normal program, this can be done for modules too: you simply append the `-l`XXX arguments for the desired libraries when you create the shared module object. To make sure a module is linked to the `libwhatever.so` library when it is `dlopen()`ed, try:

> `

>     % gcc -shared -Xlinker -x -o pam_module.so pam_module.o -lwhatever

> `

## 5.4 Added requirements for statically loaded modules.

Modules may be statically linked into libpam. This should be true of all the modules distributed with the basic Linux-PAM distribution. To be statically linked, a module needs to export information about the functions it contains in a manner that does not clash with other modules.

The extra code necessary to build a static module should be delimited with `#ifdef PAM_STATIC` and `#endif`. The static code should do the following:

- Define a single structure, `struct pam_module`, called `_pam_modname_modstruct`, where `modname` is the name of the module as used in the filesystem but without the leading directory name (generally `/usr/lib/security/` or the suffix (generally `.so`).

As a simple example, consider the following module code which defines a module that can be compiled to be static or dynamic:

> `

>     #include <stdio.h>                                    /* for NULL define */

>     #define PAM_SM_PASSWORD         /* the only pam_sm_... function declared */
>     #include <security/pam_modules.h>

>     PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
>                                     int argc, const char **argv)
>     {
>          return PAM_SUCCESS;
>     }

>     #ifdef PAM_STATIC             /* for the case that this module is static */

>     struct pam_module _pam_modname_modstruct = {       /* static module data */
>          "pam_modname",
>          NULL,
>          NULL,
>          NULL,
>          NULL,
>          NULL,
>          pam_sm_chauthtok,
>     };

>     #endif                                                 /* end PAM_STATIC */

> `

To be linked with libpam, staticly-linked modules must be built from within the `Linux-PAM-X.YY/modules/` subdirectory of the Linux-PAM source directory as part of a normal build of the Linux-PAM system.

The Makefile, for the module in question, must execute the `register_static` shell script that is located in the `Linux-PAM-X.YY/modules/` subdirectory. This is to ensure that the module is properly registered with libpam.

The two manditory arguments to `register_static` are the title, and the pathname of the object file containing the module's code. The pathname is specified relative to the `Linux-PAM-X.YY/modules` directory. The pathname may be an empty string---this is for the case that a single object file needs to register more than one `struct pam_module`. In such a case, exactly one call to `register_static` must indicate the object file.

Here is an example; a line in the Makefile might look like this:

> `

>     register:
>     ifdef STATIC
>             (cd ..; ./register_static pam_modname pam_modname/pam_modname.o)
>     endif

> `

For some further examples, see the `modules` subdirectory of the current Linux-PAM distribution.

Previous Next Table of Contents