# This publication is intellectual property of Novell Inc. and Canonical
# Ltd. Its contents can be duplicated, either in part or in whole, provided
# that a copyright label is visibly located on each copy.
#
# All information found in this book has been compiled with utmost
# attention to detail. However, this does not guarantee complete accuracy.
# Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators
# shall be held liable for possible errors or the consequences thereof.
#
# Many of the software and hardware descriptions cited in this book
# are registered trademarks. All trade names are subject to copyright
# restrictions and may be registered trade marks. SUSE LINUX GmbH
# and Canonical Ltd. essentially adhere to the manufacturer's spelling.
#
# Names of products and trademarks appearing in this book (with or without
# specific notation) are likewise subject to trademark and trade protection
# laws and may thus fall under copyright restrictions.
#


=pod

=head1 NAME

aa_change_hat  - change to or from a "hat" within a AppArmor profile

=head1 SYNOPSIS

B<#include E<lt>sys/apparmor.hE<gt>>

B<int aa_change_hat (char *subprofile, unsigned long magic_token);>

B<int aa_change_hatv (char *subprofiles[], unsigned long magic_token);>

B<int aa_change_hat_vargs (unsigned long magic_token, ...);>

Link with B<-lapparmor> when compiling.

=head1 DESCRIPTION

An AppArmor profile applies to an executable program; if a portion of
the program needs different access permissions than other portions,
the program can "change hats" to a different role, also known as a
subprofile.

To change into a new hat, it calls one of the family of change_hat
functions to do so. It passes in a pointer to the I<subprofile> which it
wants to change into, and a 64bit I<magic_token>.  The I<magic_token>
is used to return out of the subprofile at a later time.

The aa_change_hat() function allows specifying the name of a single
I<subprofile> that the application wants to change into.  A pointer to the
name of the I<subprofile> is passed along with the I<magic_token>.  If the
profile is not present the call will fail with the appropriate error.

The aa_change_hatv() function allows passing a I<NULL> terminated vector
of pointers to I<subprofile> names which will be tried in order.  The
first I<subprofile> in the vector that exists will be transitioned to
and if none of the I<subprofiles> exist the call will fail with the
appropriate error.

The aa_change_hat_vargs() function is a convenience wrapper for the
aa_change_hatv() function.  After the I<magic_token> it takes an arbitrary
number of pointers to I<subprofile> names.  Similar to execl(3),
aa_change_hat_vargs() assembles the list of I<subprofile> names into a
vector and calls aa_change_hatv().

If a program wants to return out of the current subprofile to the
original profile, it calls aa_change_hat() with a pointer to NULL as
the I<subprofile>, and the original I<magic_token> value. If the
I<magic_token> does not match the original I<magic_token> passed into the
kernel when the program entered the subprofile, the change back to the
original profile will not happen, and the current task will be killed.
If the I<magic_token> matches the original token, then the process will
change back to the original profile.


As both read(2) and write(2) are mediated, a file must be listed in a
subprofile definition if the file is to be accessed while the process
is in a "hat".

=head1 RETURN VALUE

On success zero is returned. On error, -1 is returned, and
errno(3) is set appropriately.

=head1 ERRORS

=over 4

=item B<EINVAL>

The apparmor kernel module is not loaded or the communication via the
F</proc/*/attr/current> file did not conform to protocol.

=item B<ENOMEM>

Insufficient kernel memory was available.

=item B<EPERM>

The calling application is not confined by apparmor, the specified
I<subprofile> is not a I<hat profile>, the task is being ptraced and the
tracing task does not have permission to trace the specified I<subprofile> or the no_new_privs execution bit is
enabled.

=item B<ECHILD>

The application's profile has no hats defined for it.

=item B<ENOENT>

The specified I<subprofile> does not exist in this profile but other hats
are defined.

=item B<EACCES>

The specified magic token did not match, and permissions to change to
the specified I<subprofile> has been denied. This will in most situations
also result in the task being killed, to prevent brute force attacks.

=back 

=head1 EXAMPLE

The following code examples shows simple, if contrived, uses of
aa_change_hat(); a typical use of aa_change_hat() will separate
privileged portions of a process from unprivileged portions of a process,
such as keeping unauthenticated network traffic handling separate
from authenticated network traffic handling in OpenSSH or executing
user-supplied CGI scripts in apache.

The use of random(3) is simply illustrative. Use of F</dev/urandom> is
recommended.

First, a simple high-level overview of aa_change_hat() use:

 void foo (void) {
 	unsigned long magic_token;

 	/* get a random magic token value
 	from our huge entropy pool */
 	magic_token = random_function();

 	/* change into the subprofile while
 	 * we do stuff we don't trust */
 	aa_change_hat("stuff_we_dont_trust", magic_token);

 	/* Go do stuff we don't trust -- this is all
 	 * done in *this* process space, no separate
 	 * fork()/exec()'s are done. */
 	interpret_perl_stuff(stuff_from_user);

 	/* now change back to our original profile */
 	aa_change_hat(NULL, magic_token);
 }

Second, an example to show that files not listed in a subprofile ("hat")
aren't accessible after an aa_change_hat() call:

 #include <stdlib.h>
 #include <string.h>
 #include <sys/apparmor.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <unistd.h>


 int main(int argc, char *argv[]) {
        int fd;
        unsigned long tok;
        char buf[10];

        /* random() is a poor choice */
        tok = random();

        /* open /etc/passwd outside of any hat */
        if ((fd=open("/etc/passwd", O_RDONLY)) < 0)
                perror("Failure opening /etc/passwd");

        /* confirm for ourselves that we can really read /etc/passwd */
        memset(&buf, 0, 10);
        if (read(fd, &buf, 10) == -1) {
                perror("Failure reading /etc/passwd pre-hat");
                _exit(1);
        }
        buf[9] = '\0';
        printf("/etc/passwd: %s\n", buf);

        /* change hat to the "hat" subprofile, which should not have
         * read access to /etc/passwd -- even though we have a valid
         * file descriptor at the time of the aa_change_hat() call. */
        if (aa_change_hat("hat", tok)) {
                perror("Failure changing hat -- aborting");
                _exit(1);
        }

        /* confirm that we cannot read /etc/passwd */
        lseek(fd,0,SEEK_SET);
        memset(&buf, 0, 10);
        if (read(fd, &buf, 10) == -1)
                perror("Failure reading /etc/passwd post-hat");
        buf[9] = '\0';
        printf("/etc/passwd: %s\n", buf);

        return 0;
 }


This code example requires the following profile to be loaded with
apparmor_parser(8):

 /tmp/ch {
   /etc/ld.so.cache               mr,
   /etc/locale/**                 r,
   /etc/localtime                 r,
   /usr/share/locale/**           r,
   /usr/share/zoneinfo/**         r,
   /usr/lib/locale/**             mr,
   /usr/lib/gconv/*.so            mr,
   /usr/lib/gconv/gconv-modules*  mr,

   /lib/ld-*.so*         mrix,
   /lib/libc*.so*        mr,
   /lib/libapparmor*.so* mr,
   /dev/pts/*            rw,
   /tmp/ch               mr,

   /etc/passwd           r,

   ^hat {
     /dev/pts/*     rw,
   }
 }


The output when run:

 $ /tmp/ch
 /etc/passwd: root:x:0:
 Failure reading /etc/passwd post-hat: Permission denied
 /etc/passwd:
 $


=head1 BUGS

None known. If you find any, please report them at
L<https://gitlab.com/apparmor/apparmor/-/issues>. Note that
aa_change_hat(2) provides no memory barriers between different areas of a
program; if address space separation is required, then separate processes
should be used.

=head1 SEE ALSO

apparmor(7), apparmor.d(5), apparmor_parser(8), aa_change_profile(2),
aa_getcon(2) and
L<https://wiki.apparmor.net>.

=cut
