Solaris Trusted Extensions Developer's Guide

Chapter 4 Printing and the Label APIs

Printing is one type of service that needs to be label-aware. This chapter introduces the Solaris Trusted Extensions label APIs by using as an example the multilevel printing service that was developed for Trusted Extensions.

This chapter covers the following topics:

Printing Labeled Output

Typically, printers are shared resources. Multilevel printing allows users who are operating at different security levels to share a printer, subject to the restrictions of the security policy. The printing service is also label-aware so that labels can be clearly marked on printed documents.

You can assume the System Administrator role in role-based access control (RBAC) to configure a printer so that the output is labeled. The session label at which the print job is initiated is printed on the banner and trailer pages. The label of the session is also added to the header and footer of every printed page. The labels can be printed because of a printing adapter. The Trusted Extensions printing adapter determines the host label or the zone label at which the print request was initiated. The adapter passes along this label information with the print job to enable the printed output to be labeled.

Designing a Label-Aware Application

Most applications do not need to be label-aware. Therefore, most Solaris software applications run under Trusted Extensions without modification. The Trusted Extensions label-based access restriction is designed to operate in a way that is consistent with Solaris OS standards. Generally, any process that you bind to a multilevel port needs to be label-aware because it receives data at multiple labels and is trusted to enforce the security policy.

For example, an application might not be able to access a resource because the application is running at a label that is lower than the required resource. However, an attempt to access that resource does not result in a special error condition. Instead, the application might issue a File not found error. Or, an application might attempt to access information that has a higher label than the application is allowed to access. However, the security policy dictates that without sufficient privileges, an application cannot be aware of the existence of a resource with a higher label. Therefore, if an application attempts to access a resource with a label that is higher than the application's label, the resulting error condition is not label-specific. The error message is the same as the error message that is returned to an application that tries to access a resource that does not exist. The lack of “special error conditions” helps to enforce security principles.

In Trusted Extensions, the operating system, not the application, enforces the security policy. This security policy is called the the mandatory access control (MAC) policy. For example, an application does not determine if a protected resource is accessible. Ultimately, the operating system enforces the MAC policy. If an application does not have sufficient privileges to access a resource, the resource is not available to the application. Thus, an application does not need to know anything about labels to access labeled resources.

Similarly, most label-aware applications must be designed so that they can operate in a consistent manner with applications that are not label-aware. Label-aware applications must behave in essentially the same way in environments that involve only a single label, in environments that are unlabeled, and in environments that involve multiple labels. An example of a single-label environment is when a user session with a given label mounts a device at the same label. In an unlabeled environment, a label is not explicitly set, but a default label is specified in the tnrhdb database. See the smtnrhdb(1M) man page.

Understanding the Multilevel Printing Service

Because the printing service accepts requests from processes that operate at different labels, printing must be label-aware. Ordinarily, MAC allows access only to resources that are at the same labels at which the user is operating. Even when print requests are issued only at the same label, printing should be label-aware to enable the printed output to display labels on the printed page.

To handle labels, the printing service must perform these essential functions:

get_peer_label() Label-Aware Function

The get_peer_label() function in the lp/lib/lp/tx.c file implements the logic of multilevel printing in Trusted Extensions. The following sections describe this function and step you through its implementation.

In Trusted Extensions software, much of the logic for handling labels in the printing service is in the get_peer_label() function. This function obtains the credential of the remote process in a ucred_t data structure and extracts the label from the credential.

The following shows the get_peer_label() code.

int
get_peer_label(int fd, char **slabel)
{
	if (is_system_labeled()) {
		ucred_t *uc = NULL;
		m_label_t *sl;
		char *pslabel = NULL; /* peer's slabel */

		if ((fd < 0) || (slabel == NULL)) {
			errno = EINVAL;
			return (-1);
		}
	
		if (getpeerucred(fd, &uc) == -1)
			return (-1);
	
		sl = ucred_getlabel(uc);
		if (label_to_str(sl, &pslabel, M_INTERNAL, DEF_NAMES) != 0)
			syslog(LOG_WARNING, "label_to_str(): %m");
		ucred_free(uc);
	
		if (pslabel != NULL) {
			syslog(LOG_DEBUG, "get_peer_label(%d, %s): becomes %s",
				fd, (*slabel ? *slabel : "NULL"), pslabel);
			if (*slabel != NULL)
				free(*slabel);
			*slabel = strdup(pslabel);
		}
	}
	
	return (0);
}

Determining Whether the Printing Service Is Running in a Labeled Environment

The printing service is designed to work in labeled and unlabeled environments. Therefore, the printing application must determine when the label of a remote host should be requested and whether the label should be applied. The printing process first checks its own environment. Is the process running in a label-aware environment?

Note that the application does not first determine whether the remote request is labeled. Instead, the printing application determines if its own environment is labeled. If the application is not running on a labeled host, the MAC policy prevents the printing application from receiving labeled requests.

The printing service uses the is_system_labeled() function to determine whether the process is running in a labeled environment. For information about this function, see the is_system_labeled(3C) man page.

This code excerpt shows how to determine whether the application is running in a labeled environment:

if (is_system_labeled()) {
	ucred_t *uc = NULL;
	m_label_t *sl;
	char *pslabel = NULL; /* peer's slabel */

	if ((fd < 0) || (slabel == NULL)) {
		errno = EINVAL;
		return (-1);
	}

If the printing adapter process is running on a system configured with Trusted Extensions, the is_system_labeled() function obtains the ucred_t credential abstraction from the remote process. The ucred_t data structure for the remote process and the peer's label are then set to NULL. The functions that return values for the credential and the peer's label fill the data structures. These data structures are discussed in the following sections.

See get_peer_label() Label-Aware Function to view the source of the entire get_peer_label() routine.

Understanding the Remote Host Credential

The Solaris OS network API provides an abstraction of a process's credentials. This credentials data is available through a network connection. The credentials are represented by the ucred_t data structure. This structure can include the label of a process.

The ucred API provides functions for obtaining the ucred_t data structure from a remote process. This API also provides functions for extracting the label from the ucred_t data structure.

Obtaining the Credential and Remote Host Label

Obtaining the label of a remote process is a two-step procedure. First, you must obtain the credential. Then, you must obtain the label from this credential.

The credential is in the ucred_t data structure of the remote process. The label is in the m_label_t data structure in the credential. After obtaining the credential of the remote process, you extract the label information from that credential.

The getpeerucred() function obtains the ucred_t credential data structure from the remote process. The ucred_getlabel() function extracts the label from the ucred_t data structure. In the get_peer_label() function, the two-step procedure is coded as follows:

if (getpeerucred(fd, &uc) == -1)
	return (-1);

sl = ucred_getlabel(uc);

See get_peer_label() Label-Aware Function to view the source of the entire get_peer_label() routine.

For information about the two functions, see the getpeerucred(3C) and ucred_getlabel(3C) man pages.

In addition to obtaining a remote host's label, you can obtain a remote host's type. To obtain the remote host type, use the tsol_getrhtype() routine. See Obtaining the Remote Host Type.

Using the label_to_str() Function

After obtaining the credential and remote host label, an application can call label_to_str() to convert the label data structure into a string. The string form of the label data structure can be used by the application.

Note that in the Trusted Extensions printing service, the label is returned as a string. The get_peer_label() function returns the string that is obtained by calling label_to_str() on the m_label_t data structure. This string value is returned in the slabel parameter of the get_peer_label() function, char** slabel.

The following code excerpt shows how the label_to_str() function is used:

sl = ucred_getlabel(uc);
if (label_to_str(sl, &pslabel, M_INTERNAL, DEF_NAMES) != 0)
	syslog(LOG_WARNING, "label_to_str(): %m");
ucred_free(uc);

if (pslabel != NULL) {
	syslog(LOG_DEBUG, "get_peer_label(%d, %s): becomes %s",
		fd, (*slabel ? *slabel : "NULL"), pslabel);
	if (*slabel != NULL)
		free(*slabel);
	*slabel = strdup(pslabel);
}

See get_peer_label() Label-Aware Function to view the source of the entire get_peer_label() routine.

Handling Memory Management

As shown in get_peer_label() Label-Aware Function, labels are often dynamically allocated. The functions str_to_label(), label_to_str(), getdevicerange(), and other functions allocate memory that must be freed by the caller. The following man pages for these functions describe the memory allocation requirements:

Using the Returned Label String

The get_peer_label() function extracts the label from a remote host and returns that label as a string. The printing application, as is typical of label-aware applications, uses the label for the following purposes:

Validating the Label Request Against the Printer's Label Range

In the printing application, the code for validating the label is contained in the lp/cmd/lpsched/validate.c file.

Some types of applications need to compare two given labels. For example, an application might need to determine if one label strictly dominates another label. These applications use API functions that compare one label to another label.

The printing application, however, is based on a range of labels. A printer is configured to accept printing requests from a range of different labels. Therefore, the printing application uses API functions that check a label against a range. The application checks that the label from the remote host falls within the range of labels that the printer allows.

In the validate.c file, the printing application uses the blinrange() function to check the remote host's label against the label range of the printer. This check is made within the tsol_check_printer_label_range() function, as shown here:

static int
tsol_check_printer_label_range(char *slabel, const char *printer)
{
	int			in_range = 0;
	int			err = 0;
	blrange_t		*range;
	m_label_t	*sl = NULL;

	if (slabel == NULL)
		return (0);

	if ((err =
	    (str_to_label(slabel, &sl, USER_CLEAR, L_NO_CORRECTION, &in_range)))
	    == -1) {
		/* str_to_label error on printer max label */
		return (0);
	}
	if ((range = getdevicerange(printer)) == NULL) {
		m_label_free(sl);
		return (0);
	}

	/* blinrange returns true (1) if in range, false (0) if not */
	in_range = blinrange(sl, range);

	m_label_free(sl);
	m_label_free(range->lower_bound);
	m_label_free(range->upper_bound);
	free(range);

	return (in_range);
}

The tsol_check_printer_label_range() function takes as parameters the label returned by the get_peer_label() function and the name of the printer.

Before comparing the labels, tsol_check_printer_label_range() converts the string into a label by using the str_to_label() function.

The label type is set to USER_CLEAR, which produces the clearance label of the associated object. The clearance label ensures that the appropriate level of label is used in the range check that the blinrange() function performs.

The sl label that is obtained from str_to_label() is checked to determine whether the remote host's label, slabel, is within the range of the requested device, that is, the printer. This label is tested against the printer's label. The printer's range is obtained by calling the getdevicerange() function for the selected printer. The range is returned as a blrange_t data structure.

The printer's label range in the blrange_t data structure is passed into the blinrange() function, along with the clearance label of the requester. See the blinrange(3TSOL) man page.

The following code excerpt shows the _validate() function in the validate.c file. This function is used to find a printer to handle a printing request. This code compares the user ID and the label associated with the request against the set of allowed users and the label range that is associated with each printer.

/*
 * If a single printer was named, check the request against it.
 * Do the accept/reject check late so that we give the most
 * useful information to the user.
 */
if (pps) {
	(pc = &single)->pps = pps;

	/* Does the printer allow access to the user? */
	if (!CHKU(prs, pps)) {
		ret = MDENYDEST;
		goto Return;
	}

	/* Check printer label range */
	if (is_system_labeled() && prs->secure->slabel != NULL) {
		if (tsol_check_printer_label_range(prs->secure->slabel,
			pps->printer->name) == 0) {
			ret = MDENYDEST;
			goto Return;
		}
	}