1 /******************************************************************************
2 * A module for Linux-PAM that will set the default namespace after
3 * establishing a session via PAM.
5 * (C) Copyright IBM Corporation 2005
6 * (C) Copyright Red Hat 2006
9 * Written by: Janak Desai <janak@us.ibm.com>
10 * With Revisions by: Steve Grubb <sgrubb@redhat.com>
11 * Derived from a namespace setup patch by Chad Sellers <cdselle@tycho.nsa.gov>
13 * Permission is hereby granted, free of charge, to any person obtaining a
14 * copy of this software and associated documentation files (the "Software"),
15 * to deal in the Software without restriction, including without limitation
16 * on the rights to use, copy, modify, merge, publish, distribute, sub
17 * license, and/or sell copies of the Software, and to permit persons to whom
18 * the Software is furnished to do so, subject to the following conditions:
20 * The above copyright notice and this permission notice (including the next
21 * paragraph) shall be included in all copies or substantial portions of the
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
27 * IBM AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30 * DEALINGS IN THE SOFTWARE.
34 #error THIS CODE IS KNOWN TO WORK ONLY ON LINUX !!!
40 #include <stdio_ext.h>
51 #include <sys/types.h>
53 #include <sys/resource.h>
54 #include <sys/mount.h>
59 #include "security/pam_modules.h"
60 #include "security/pam_modutil.h"
61 #include "security/pam_ext.h"
65 #include <selinux/selinux.h>
69 #define CLONE_NEWNS 0x00020000 /* Flag to create new namespace */
75 #ifndef PAM_NAMESPACE_CONFIG
76 #define PAM_NAMESPACE_CONFIG "/etc/security/namespace.conf"
79 #ifndef NAMESPACE_INIT_SCRIPT
80 #define NAMESPACE_INIT_SCRIPT "/etc/security/namespace.init"
83 #define PAMNS_DEBUG 0x00000100 /* Running in debug mode */
84 #define PAMNS_SELINUX_ENABLED 0x00000400 /* SELinux is enabled */
85 #define PAMNS_CTXT_BASED_INST 0x00000800 /* Context based instance needed */
86 #define PAMNS_GEN_HASH 0x00002000 /* Generate md5 hash for inst names */
87 #define PAMNS_IGN_CONFIG_ERR 0x00004000 /* Ignore format error in conf file */
88 #define PAMNS_IGN_INST_PARENT_MODE 0x00008000 /* Ignore instance parent mode */
91 * Polyinstantiation method options, based on user, security context
101 * Depending on the application using this namespace module, we
102 * may need to unmount priviously bind mounted instance directory.
103 * Applications such as login and sshd, that establish a new
104 * session unmount of instance directory is not needed. For applications
105 * such as su and newrole, that switch the identity, this module
106 * has to unmount previous instance directory first and re-mount
107 * based on the new indentity. For other trusted applications that
108 * just want to undo polyinstantiation, only unmount of previous
109 * instance directory is needed.
118 * Structure that holds information about a directory to polyinstantiate
121 char dir[PATH_MAX]; /* directory to polyinstantiate */
122 char instance_prefix[PATH_MAX]; /* prefix for instance dir path name */
123 enum polymethod method; /* method used to polyinstantiate */
124 unsigned int num_uids; /* number of override uids */
125 uid_t *uid; /* list of override uids */
126 struct polydir_s *next; /* pointer to the next polydir entry */
129 struct instance_data {
130 pam_handle_t *pamh; /* The pam handle for this instance */
131 struct polydir_s *polydirs_ptr; /* The linked list pointer */
132 char user[LOGIN_NAME_MAX]; /* User name */
133 uid_t uid; /* The uid of the user */
134 unsigned long flags; /* Flags for debug, selinux etc */
138 * Adds an entry for a polyinstantiated directory to the linked list of
139 * polyinstantiated directories. It is called from process_line() while
140 * parsing the namespace configuration file.
142 static int add_polydir_entry(struct instance_data *idata,
143 const struct polydir_s *ent)
145 struct polydir_s *pent;
149 * Allocate an entry to hold information about a directory to
150 * polyinstantiate, populate it with information from 2nd argument
151 * and add the entry to the linked list of polyinstantiated
154 pent = (struct polydir_s *) malloc(sizeof(struct polydir_s));
159 strcpy(pent->dir, ent->dir);
160 strcpy(pent->instance_prefix, ent->instance_prefix);
161 pent->method = ent->method;
162 pent->num_uids = ent->num_uids;
166 pent->uid = (uid_t *) malloc(ent->num_uids * sizeof(uid_t));
171 for (i = 0, pptr = pent->uid, eptr = ent->uid; i < ent->num_uids;
177 /* Now attach to linked list */
179 if (idata->polydirs_ptr == NULL)
180 idata->polydirs_ptr = pent;
182 struct polydir_s *tail;
184 tail = idata->polydirs_ptr;
195 * Deletes all the entries in the linked list.
197 static void del_polydir_list(struct polydir_s *polydirs_ptr)
199 struct polydir_s *dptr = polydirs_ptr;
202 struct polydir_s *tptr = dptr;
211 * Called from parse_config_file, this function processes a single line
212 * of the namespace configuration file. It skips over comments and incomplete
213 * or malformed lines. It processes a valid line with information on
214 * polyinstantiating a directory by populating appropriate fields of a
215 * polyinstatiated directory structure and then calling add_polydir_entry to
216 * add that entry to the linked list of polyinstantiated directories.
218 static int process_line(char *line, const char *home,
219 struct instance_data *idata)
221 const char *dir, *instance_prefix;
222 const char *method, *uids;
224 struct polydir_s poly;
231 * skip the leading white space
233 while (*line && isspace(*line))
237 * Rip off the comments
239 tptr = strchr(line,'#');
244 * Rip off the newline char
246 tptr = strchr(line,'\n');
257 * Initialize and scan the five strings from the line from the
258 * namespace configuration file.
260 dir = strtok_r(line, " \t", &tptr);
262 pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing polydir");
265 instance_prefix = strtok_r(NULL, " \t", &tptr);
266 if (instance_prefix == NULL) {
267 pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing instance_prefix");
270 method = strtok_r(NULL, " \t", &tptr);
271 if (method == NULL) {
272 pam_syslog(idata->pamh, LOG_NOTICE, "Invalid line missing method");
277 * Only the uids field is allowed to be blank, to indicate no
278 * override users for polyinstantiation of that directory. If
279 * any of the other fields are blank, the line is incomplete so
282 uids = strtok_r(NULL, " \t", &tptr);
285 * If the directory being polyinstantiated is the home directory
286 * of the user who is establishing a session, we have to swap
287 * the "$HOME" string with the user's home directory that is
288 * passed in as an argument.
290 if (strcmp(dir, "$HOME") == 0) {
295 * Expand $HOME and $USER in instance dir prefix
297 if ((tptr = strstr(instance_prefix, "$USER")) != 0) {
298 /* FIXME: should only support this if method is USER or BOTH */
299 char *expanded = alloca(strlen(idata->user) + strlen(instance_prefix)-5+1);
301 sprintf(expanded, "%s%s%s", instance_prefix, idata->user, tptr+5);
302 instance_prefix = expanded;
304 if ((tptr = strstr(instance_prefix, "$HOME")) != 0) {
305 char *expanded = alloca(strlen(home)+strlen(instance_prefix)-5+1);
307 sprintf(expanded, "%s%s%s", instance_prefix, home, tptr+5);
308 instance_prefix = expanded;
312 * Ensure that all pathnames are absolute path names.
314 if ((dir[0] != '/') || (instance_prefix[0] != '/')) {
315 pam_syslog(idata->pamh, LOG_NOTICE,"Pathnames must start with '/'");
318 if (strstr(dir, "..") || strstr(instance_prefix, "..")) {
319 pam_syslog(idata->pamh, LOG_NOTICE,"Pathnames must not contain '..'");
324 * Populate polyinstantiated directory structure with appropriate
325 * pathnames and the method with which to polyinstantiate.
327 if (strlen(dir) >= sizeof(poly.dir)
328 || strlen(instance_prefix) >= sizeof(poly.instance_prefix)) {
329 pam_syslog(idata->pamh, LOG_NOTICE, "Pathnames too long");
331 strcpy(poly.dir, dir);
332 strcpy(poly.instance_prefix, instance_prefix);
333 if (strcmp(method, "user") == 0)
336 else if (strcmp(method, "context") == 0) {
337 if (idata->flags & PAMNS_CTXT_BASED_INST)
338 poly.method = CONTEXT;
341 } else if (strcmp(method, "both") == 0) {
342 if (idata->flags & PAMNS_CTXT_BASED_INST)
350 pam_syslog(idata->pamh, LOG_NOTICE, "Illegal method");
355 * If the line in namespace.conf for a directory to polyinstantiate
356 * contains a list of override users (users for whom polyinstantiation
357 * is not performed), read the user ids, convert names into uids, and
358 * add to polyinstantiated directory structure.
362 const char *ustr, *sstr;
365 for (count = 0, ustr = sstr = uids; sstr; ustr = sstr + 1, count++)
366 sstr = strchr(ustr, ',');
368 poly.num_uids = count;
369 poly.uid = (uid_t *) malloc(count * sizeof (uid_t));
371 if (uidptr == NULL) {
372 pam_syslog(idata->pamh, LOG_NOTICE, "out of memory");
377 for (i = 0; i < count; i++) {
380 tptr = strchr(ustr, ',');
384 pwd = getpwnam(ustr);
385 *uidptr = pwd->pw_uid;
394 * Add polyinstantiated directory structure to the linked list
395 * of all polyinstantiated directory structures.
397 if (add_polydir_entry(idata, &poly) < 0) {
398 pam_syslog(idata->pamh, LOG_ERR, "Allocation Error");
399 retval = PAM_SERVICE_ERR;
406 if (idata->flags & PAMNS_IGN_CONFIG_ERR)
409 retval = PAM_SERVICE_ERR;
416 * Parses /etc/security/namespace.conf file to build a linked list of
417 * polyinstantiated directory structures of type polydir_s. Each entry
418 * in the linked list contains information needed to polyinstantiate
421 static int parse_config_file(struct instance_data *idata)
430 if (idata->flags & PAMNS_DEBUG)
431 pam_syslog(idata->pamh, LOG_DEBUG, "Parsing config file %s",
432 PAM_NAMESPACE_CONFIG);
435 * Extract the user's home directory to resolve $HOME entries
436 * in the namespace configuration file.
438 cpwd = getpwnam(idata->user);
440 pam_syslog(idata->pamh, LOG_ERR,
441 "Error getting home dir for '%s'", idata->user);
442 return PAM_SESSION_ERR;
444 home = strdupa(cpwd->pw_dir);
447 * Open configuration file, read one line at a time and call
448 * process_line to process each line.
450 fil = fopen(PAM_NAMESPACE_CONFIG, "r");
452 pam_syslog(idata->pamh, LOG_ERR, "Error opening config file");
453 return PAM_SERVICE_ERR;
456 /* Use unlocked IO */
457 __fsetlocking(fil, FSETLOCKING_BYCALLER);
459 /* loop reading the file */
460 while (getline(&line, &len, fil) > 0) {
461 retval = process_line(line, home, idata);
463 pam_syslog(idata->pamh, LOG_ERR,
464 "Error processing conf file line %s", line);
467 return PAM_SERVICE_ERR;
473 /* All done...just some debug stuff */
474 if (idata->flags & PAMNS_DEBUG) {
475 struct polydir_s *dptr = idata->polydirs_ptr;
479 pam_syslog(idata->pamh, LOG_DEBUG,
480 dptr?"Configured poly dirs:":"No configured poly dirs");
482 pam_syslog(idata->pamh, LOG_DEBUG, "dir='%s' iprefix='%s' meth=%d",
483 dptr->dir, dptr->instance_prefix, dptr->method);
484 for (i = 0, iptr = dptr->uid; i < dptr->num_uids; i++, iptr++)
485 pam_syslog(idata->pamh, LOG_DEBUG, "override user %d ", *iptr);
495 * This funtion returns true if a given uid is present in the polyinstantiated
496 * directory's list of override uids. If the uid is one of the override
497 * uids for the polyinstantiated directory, polyinstantiation is not
498 * performed for that user for that directory.
500 static int ns_override(struct polydir_s *polyptr, struct instance_data *idata)
504 if (idata->flags & PAMNS_DEBUG)
505 pam_syslog(idata->pamh, LOG_DEBUG,
506 "Checking for ns override in dir %s for uid %d",
507 polyptr->dir, idata->uid);
509 for (i = 0; i < polyptr->num_uids; i++) {
510 if (idata->uid == polyptr->uid[i]) {
520 * poly_name returns the name of the polyinstantiated instance directory
521 * based on the method used for polyinstantiation (user, context or both)
522 * In addition, the function also returns the security contexts of the
523 * original directory to polyinstantiate and the polyinstantiated instance
527 static int poly_name(const struct polydir_s *polyptr, char **i_name,
528 security_context_t *i_context, security_context_t *origcon,
529 struct instance_data *idata)
531 static int poly_name(const struct polydir_s *polyptr, char **i_name,
532 struct instance_data *idata)
536 security_context_t scon = NULL;
537 security_class_t tclass;
543 * Get the security context of the directory to polyinstantiate.
545 rc = getfilecon(polyptr->dir, origcon);
546 if (rc < 0 || *origcon == NULL) {
547 pam_syslog(idata->pamh, LOG_ERR,
548 "Error getting poly dir context, %m");
549 return PAM_SESSION_ERR;
553 * If polyinstantiating based on security context, get current
554 * process security context, get security class for directories,
555 * and ask the policy to provide security context of the
556 * polyinstantiated instance directory.
558 if ((polyptr->method == CONTEXT) || (polyptr->method == BOTH)) {
559 rc = getexeccon(&scon);
560 if (rc < 0 || scon == NULL) {
561 pam_syslog(idata->pamh, LOG_ERR,
562 "Error getting exec context, %m");
563 return PAM_SESSION_ERR;
565 tclass = string_to_security_class("dir");
567 if (security_compute_member(scon, *origcon, tclass,
569 pam_syslog(idata->pamh, LOG_ERR,
570 "Error computing poly dir member context");
572 return PAM_SESSION_ERR;
573 } else if (idata->flags & PAMNS_DEBUG)
574 pam_syslog(idata->pamh, LOG_DEBUG,
575 "member context returned by policy %s", *i_context);
582 * Set the name of the polyinstantiated instance dir based on the
583 * polyinstantiation method.
585 switch (polyptr->method) {
587 if (asprintf(i_name, "%s", idata->user) < 0) {
589 rc = PAM_SESSION_ERR;
595 if (asprintf(i_name, "%s", *i_context) < 0) {
597 rc = PAM_SESSION_ERR;
602 if (asprintf(i_name, "%s_%s", *i_context, idata->user) < 0) {
604 rc = PAM_SESSION_ERR;
607 #endif /* WITH_SELINUX */
610 if (idata->flags & PAMNS_DEBUG)
611 pam_syslog(idata->pamh, LOG_ERR, "Unknown method");
612 rc = PAM_SESSION_ERR;
615 if ((idata->flags & PAMNS_DEBUG) && rc == PAM_SUCCESS)
616 pam_syslog(idata->pamh, LOG_DEBUG, "poly_name %s", *i_name);
623 * Create polyinstantiated instance directory (ipath).
626 static int create_dirs(const struct polydir_s *polyptr, char *ipath,
627 security_context_t icontext, security_context_t ocontext,
628 struct instance_data *idata)
630 static int create_dirs(const struct polydir_s *polyptr, char *ipath,
631 struct instance_data *idata)
634 struct stat statbuf, newstatbuf, instpbuf;
636 char *inst_parent, *trailing_slash;
638 sighandler_t osighand = NULL;
641 * stat the directory to polyinstantiate, so its owner-group-mode
642 * can be propagated to instance directory
644 if (stat(polyptr->dir, &statbuf) < 0) {
645 pam_syslog(idata->pamh, LOG_ERR, "Error stating %s, %m",
647 return PAM_SESSION_ERR;
651 * Make sure we are dealing with a directory
653 if (!S_ISDIR(statbuf.st_mode)) {
654 pam_syslog(idata->pamh, LOG_ERR, "poly dir %s is not a dir",
656 return PAM_SESSION_ERR;
660 * stat the instance parent path to make sure it exists
661 * and is a directory. Check that its mode is 000 (unless the
662 * admin explicitly instructs to ignore the instance parent
663 * mode by the "ignore_instance_parent_mode" argument).
665 inst_parent = (char *) malloc(strlen(ipath)+1);
667 pam_syslog(idata->pamh, LOG_ERR, "Error allocating pathname string");
668 return PAM_SESSION_ERR;
671 strcpy(inst_parent, ipath);
672 trailing_slash = strrchr(inst_parent, '/');
674 *trailing_slash = '\0';
676 if (stat(inst_parent, &instpbuf) < 0) {
677 pam_syslog(idata->pamh, LOG_ERR, "Error stating %s, %m", inst_parent);
679 return PAM_SESSION_ERR;
683 * Make sure we are dealing with a directory
685 if (!S_ISDIR(instpbuf.st_mode)) {
686 pam_syslog(idata->pamh, LOG_ERR, "Instance parent %s is not a dir",
689 return PAM_SESSION_ERR;
692 if ((idata->flags & PAMNS_IGN_INST_PARENT_MODE) == 0) {
693 if (instpbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO)) {
694 pam_syslog(idata->pamh, LOG_ERR, "Mode of inst parent %s not 000",
697 return PAM_SESSION_ERR;
703 * Create instance directory and set its security context to the context
704 * returned by the security policy. Set its mode and ownership
705 * attributes to match that of the original directory that is being
708 if (mkdir(ipath, S_IRUSR) < 0) {
712 pam_syslog(idata->pamh, LOG_ERR, "Error creating %s, %m",
714 return PAM_SESSION_ERR;
718 /* Open a descriptor to it to prevent races */
719 fd = open(ipath, O_DIRECTORY | O_RDONLY);
721 pam_syslog(idata->pamh, LOG_ERR, "Error opening %s, %m", ipath);
723 return PAM_SESSION_ERR;
726 /* If SE Linux is disabled, no need to label it */
727 if (idata->flags & PAMNS_SELINUX_ENABLED) {
728 /* If method is USER, icontext is NULL */
730 if (fsetfilecon(fd, icontext) < 0) {
731 pam_syslog(idata->pamh, LOG_ERR,
732 "Error setting context of %s to %s", ipath, icontext);
735 return PAM_SESSION_ERR;
738 if (fsetfilecon(fd, ocontext) < 0) {
739 pam_syslog(idata->pamh, LOG_ERR,
740 "Error setting context of %s to %s", ipath, ocontext);
743 return PAM_SESSION_ERR;
748 if (fstat(fd, &newstatbuf) < 0) {
749 pam_syslog(idata->pamh, LOG_ERR, "Error stating %s, %m",
752 return PAM_SESSION_ERR;
754 if (newstatbuf.st_uid != statbuf.st_uid ||
755 newstatbuf.st_gid != statbuf.st_gid) {
756 if (fchown(fd, statbuf.st_uid, statbuf.st_gid) < 0) {
757 pam_syslog(idata->pamh, LOG_ERR,
758 "Error changing owner for %s, %m",
762 return PAM_SESSION_ERR;
765 if (fchmod(fd, statbuf.st_mode & 07777) < 0) {
766 pam_syslog(idata->pamh, LOG_ERR, "Error changing mode for %s, %m",
770 return PAM_SESSION_ERR;
775 * Check to see if there is a namespace initialization script in
776 * the /etc/security directory. If such a script exists
777 * execute it and pass directory to polyinstantiate and instance
778 * directory as arguments.
782 osighand = signal(SIGCHLD, SIG_DFL);
783 if (osighand == SIG_ERR) {
784 pam_syslog(idata->pamh, LOG_ERR, "Cannot set signal value");
785 return PAM_SESSION_ERR;
788 if (access(NAMESPACE_INIT_SCRIPT, F_OK) == 0) {
789 if (access(NAMESPACE_INIT_SCRIPT, X_OK) < 0) {
790 if (idata->flags & PAMNS_DEBUG)
791 pam_syslog(idata->pamh, LOG_ERR,
792 "Namespace init script not executable");
793 (void) signal(SIGCHLD, osighand);
794 return PAM_SESSION_ERR;
799 if (idata->flags & PAMNS_SELINUX_ENABLED) {
800 if (setexeccon(NULL) < 0)
804 if (execl(NAMESPACE_INIT_SCRIPT, NAMESPACE_INIT_SCRIPT,
805 polyptr->dir, ipath, (char *)NULL) < 0)
807 } else if (pid > 0) {
808 while (((rc = waitpid(pid, &status, 0)) == (pid_t)-1) &&
810 if (rc == (pid_t)-1) {
811 pam_syslog(idata->pamh, LOG_ERR, "waitpid failed- %m");
812 (void) signal(SIGCHLD, osighand);
813 return PAM_SESSION_ERR;
815 if (!WIFEXITED(status) || WIFSIGNALED(status) > 0) {
816 pam_syslog(idata->pamh, LOG_ERR,
817 "Error initializing instance");
818 (void) signal(SIGCHLD, osighand);
819 return PAM_SESSION_ERR;
821 } else if (pid < 0) {
822 pam_syslog(idata->pamh, LOG_ERR,
823 "Cannot fork to run namespace init script, %m");
824 (void) signal(SIGCHLD, osighand);
825 return PAM_SESSION_ERR;
830 (void) signal(SIGCHLD, osighand);
836 * md5hash generates a hash of the passed in instance directory name.
838 static int md5hash(char **instname, struct instance_data *idata)
841 char *md5inst = NULL;
843 unsigned char inst_digest[MD5_DIGEST_LENGTH];
846 * Create MD5 hashes for instance pathname.
849 MD5((unsigned char *)*instname, strlen(*instname), inst_digest);
851 if ((md5inst = malloc(MD5_DIGEST_LENGTH * 2 + 1)) == NULL) {
852 pam_syslog(idata->pamh, LOG_ERR, "Unable to allocate buffer");
853 return PAM_SESSION_ERR;
857 for (i = 0; i < MD5_DIGEST_LENGTH; i++) {
858 snprintf(to, 3, "%02x", (unsigned int)inst_digest[i]);
869 * This function performs the namespace setup for a particular directory
870 * that is being polyinstantiated. It creates an MD5 hash of instance
871 * directory, calls create_dirs to create it with appropriate
872 * security attributes, and performs bind mount to setup the process
875 static int ns_setup(const struct polydir_s *polyptr,
876 struct instance_data *idata)
879 char *inst_dir = NULL;
880 char *instname = NULL;
883 security_context_t instcontext = NULL, origcontext = NULL;
886 if (idata->flags & PAMNS_DEBUG)
887 pam_syslog(idata->pamh, LOG_DEBUG,
888 "Set namespace for directory %s", polyptr->dir);
890 dir = strrchr(polyptr->dir, '/');
891 if (dir && strlen(dir) > 1)
895 * Obtain the name of instance pathname based on the
896 * polyinstantiation method and instance context returned by
900 retval = poly_name(polyptr, &instname, &instcontext,
901 &origcontext, idata);
903 retval = poly_name(polyptr, &instname, idata);
907 pam_syslog(idata->pamh, LOG_ERR, "Error getting instance name");
911 if ((idata->flags & PAMNS_DEBUG) &&
912 (idata->flags & PAMNS_SELINUX_ENABLED))
913 pam_syslog(idata->pamh, LOG_DEBUG, "Inst ctxt %s Orig ctxt %s",
914 instcontext, origcontext);
918 if (idata->flags & PAMNS_GEN_HASH) {
919 retval = md5hash(&instname, idata);
921 pam_syslog(idata->pamh, LOG_ERR, "Error generating md5 hash");
926 if (asprintf(&inst_dir, "%s%s", polyptr->instance_prefix, instname) < 0)
929 if (idata->flags & PAMNS_DEBUG)
930 pam_syslog(idata->pamh, LOG_DEBUG, "instance_dir %s",
934 * Create instance directory with appropriate security
935 * contexts, owner, group and mode bits.
938 retval = create_dirs(polyptr, inst_dir, instcontext,
941 retval = create_dirs(polyptr, inst_dir, idata);
945 pam_syslog(idata->pamh, LOG_ERR, "Error creating instance dir");
950 * Bind mount instance directory on top of the polyinstantiated
951 * directory to provide an instance of polyinstantiated directory
952 * based on polyinstantiated method.
954 if (mount(inst_dir, polyptr->dir, NULL, MS_BIND, NULL) < 0) {
955 pam_syslog(idata->pamh, LOG_ERR, "Error mounting %s on %s, %m",
956 inst_dir, polyptr->dir);
963 * various error exit points. Free allocated memory and set return
964 * value to indicate a pam session error.
967 retval = PAM_SESSION_ERR;
973 freecon(instcontext);
974 freecon(origcontext);
981 * This function checks to see if the current working directory is
982 * inside the directory passed in as the first argument.
984 static int cwd_in(char *dir, struct instance_data *idata)
989 if (getcwd(cwd, PATH_MAX) == NULL) {
990 pam_syslog(idata->pamh, LOG_ERR, "Can't get current dir, %m");
994 if (strncmp(cwd, dir, strlen(dir)) == 0) {
995 if (idata->flags & PAMNS_DEBUG)
996 pam_syslog(idata->pamh, LOG_DEBUG, "cwd is inside %s", dir);
999 if (idata->flags & PAMNS_DEBUG)
1000 pam_syslog(idata->pamh, LOG_DEBUG, "cwd is outside %s", dir);
1008 * This function checks to see if polyinstantiation is needed for any
1009 * of the directories listed in the configuration file. If needed,
1010 * cycles through all polyinstantiated directory entries and calls
1011 * ns_setup to setup polyinstantiation for each one of them.
1013 static int setup_namespace(struct instance_data *idata, enum unmnt_op unmnt)
1015 int retval = 0, need_poly = 0, changing_dir = 0;
1016 char *cptr, *fptr, poly_parent[PATH_MAX];
1017 struct polydir_s *pptr;
1019 if (idata->flags & PAMNS_DEBUG)
1020 pam_syslog(idata->pamh, LOG_DEBUG, "Set up namespace for pid %d",
1024 * Cycle through all polyinstantiated directory entries to see if
1025 * polyinstantiation is needed at all.
1027 for (pptr = idata->polydirs_ptr; pptr; pptr = pptr->next) {
1028 if (ns_override(pptr, idata)) {
1029 if (idata->flags & PAMNS_DEBUG)
1030 pam_syslog(idata->pamh, LOG_DEBUG,
1031 "Overriding poly for user %d for dir %s",
1032 idata->uid, pptr->dir);
1035 if (idata->flags & PAMNS_DEBUG)
1036 pam_syslog(idata->pamh, LOG_DEBUG,
1037 "Need poly ns for user %d for dir %s",
1038 idata->uid, pptr->dir);
1045 * If polyinstnatiation is needed, call the unshare system call to
1046 * disassociate from the parent namespace.
1049 if (unshare(CLONE_NEWNS) < 0) {
1050 pam_syslog(idata->pamh, LOG_ERR,
1051 "Unable to unshare from parent namespace, %m");
1052 return PAM_SESSION_ERR;
1058 * Again cycle through all polyinstantiated directories, this time,
1059 * call ns_setup to setup polyinstantiation for a particular entry.
1061 for (pptr = idata->polydirs_ptr; pptr; pptr = pptr->next) {
1062 if (ns_override(pptr, idata))
1065 if (idata->flags & PAMNS_DEBUG)
1066 pam_syslog(idata->pamh, LOG_DEBUG,
1067 "Setting poly ns for user %d for dir %s",
1068 idata->uid, pptr->dir);
1070 if ((unmnt == UNMNT_REMNT) || (unmnt == UNMNT_ONLY)) {
1072 * Check to see if process current directory is in the
1073 * bind mounted instance_parent directory that we are trying to
1076 if ((changing_dir = cwd_in(pptr->dir, idata)) < 0) {
1077 return PAM_SESSION_ERR;
1078 } else if (changing_dir) {
1079 if (idata->flags & PAMNS_DEBUG)
1080 pam_syslog(idata->pamh, LOG_DEBUG, "changing cwd");
1083 * Change current working directory to the parent of
1084 * the mount point, that is parent of the orig
1085 * directory where original contents of the polydir
1086 * are available from
1088 strcpy(poly_parent, pptr->dir);
1089 fptr = strchr(poly_parent, '/');
1090 cptr = strrchr(poly_parent, '/');
1091 if (fptr && cptr && (fptr == cptr))
1092 strcpy(poly_parent, "/");
1095 if (chdir(poly_parent) < 0) {
1096 pam_syslog(idata->pamh, LOG_ERR,
1097 "Can't chdir to %s, %m", poly_parent);
1101 if (umount(pptr->dir) < 0) {
1102 int saved_errno = errno;
1103 pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m",
1105 if (saved_errno != EINVAL)
1106 return PAM_SESSION_ERR;
1107 } else if (idata->flags & PAMNS_DEBUG)
1108 pam_syslog(idata->pamh, LOG_DEBUG, "Umount succeeded %s",
1112 if (unmnt != UNMNT_ONLY) {
1113 retval = ns_setup(pptr, idata);
1114 if (retval != PAM_SUCCESS)
1125 * Orig namespace. This function is called from when closing a pam
1126 * session. If authorized, it unmounts instance directory.
1128 static int orig_namespace(struct instance_data *idata)
1130 struct polydir_s *pptr;
1132 if (idata->flags & PAMNS_DEBUG)
1133 pam_syslog(idata->pamh, LOG_DEBUG, "orig namespace for pid %d",
1137 * Cycle through all polyinstantiated directories from the namespace
1138 * configuration file to see if polyinstantiation was performed for
1139 * this user for each of the entry. If it was, try and unmount
1140 * appropriate polyinstantiated instance directories.
1142 for (pptr = idata->polydirs_ptr; pptr; pptr = pptr->next) {
1143 if (ns_override(pptr, idata))
1146 if (idata->flags & PAMNS_DEBUG)
1147 pam_syslog(idata->pamh, LOG_DEBUG,
1148 "Unmounting instance dir for user %d & dir %s",
1149 idata->uid, pptr->dir);
1151 if (umount(pptr->dir) < 0) {
1152 pam_syslog(idata->pamh, LOG_ERR, "Unmount of %s failed, %m",
1154 return PAM_SESSION_ERR;
1155 } else if (idata->flags & PAMNS_DEBUG)
1156 pam_syslog(idata->pamh, LOG_DEBUG, "Unmount of %s succeeded",
1166 * This function checks if the calling program has requested context
1167 * change by calling setexeccon(). If context change is not requested
1168 * then it does not make sense to polyinstantiate based on context.
1169 * The return value from this function is used when selecting the
1170 * polyinstantiation method. If context change is not requested then
1171 * the polyinstantiation method is set to USER, even if the configuration
1172 * file lists the method as "context" or "both".
1174 static int ctxt_based_inst_needed(void)
1176 security_context_t scon = NULL;
1179 rc = getexeccon(&scon);
1180 if (rc < 0 || scon == NULL)
1191 * Entry point from pam_open_session call.
1193 PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED,
1194 int argc, const char **argv)
1197 struct instance_data idata;
1200 enum unmnt_op unmnt = NO_UNMNT;
1202 /* init instance data */
1204 idata.polydirs_ptr = NULL;
1207 if (is_selinux_enabled())
1208 idata.flags |= PAMNS_SELINUX_ENABLED;
1209 if (ctxt_based_inst_needed())
1210 idata.flags |= PAMNS_CTXT_BASED_INST;
1213 /* Parse arguments. */
1214 for (i = 0; i < argc; i++) {
1215 if (strcmp(argv[i], "debug") == 0)
1216 idata.flags |= PAMNS_DEBUG;
1217 if (strcmp(argv[i], "gen_hash") == 0)
1218 idata.flags |= PAMNS_GEN_HASH;
1219 if (strcmp(argv[i], "ignore_config_error") == 0)
1220 idata.flags |= PAMNS_IGN_CONFIG_ERR;
1221 if (strcmp(argv[i], "ignore_instance_parent_mode") == 0)
1222 idata.flags |= PAMNS_IGN_INST_PARENT_MODE;
1223 if (strcmp(argv[i], "unmnt_remnt") == 0)
1224 unmnt = UNMNT_REMNT;
1225 if (strcmp(argv[i], "unmnt_only") == 0)
1227 if (strcmp(argv[i], "require_selinux") == 0) {
1228 if (~(idata.flags & PAMNS_SELINUX_ENABLED)) {
1229 pam_syslog(idata.pamh, LOG_ERR,
1230 "selinux_required option given and selinux is disabled");
1231 return PAM_SESSION_ERR;
1235 if (idata.flags & PAMNS_DEBUG)
1236 pam_syslog(idata.pamh, LOG_DEBUG, "open_session - start");
1239 * Lookup user and fill struct items
1241 retval = pam_get_item(idata.pamh, PAM_USER, (void*) &user_name );
1242 if ( user_name == NULL || retval != PAM_SUCCESS ) {
1243 pam_syslog(idata.pamh, LOG_ERR, "Error recovering pam user name");
1244 return PAM_SESSION_ERR;
1247 pwd = getpwnam(user_name);
1249 pam_syslog(idata.pamh, LOG_ERR, "user unknown '%s'", user_name);
1250 return PAM_SESSION_ERR;
1254 * Add the user info to the instance data so we can refer to them later.
1257 strncat(idata.user, user_name, sizeof(idata.user));
1258 idata.uid = pwd->pw_uid;
1261 * Parse namespace configuration file which lists directories to
1262 * polyinstantiate, directory where instance directories are to
1263 * be created and the method used for polyinstantiation.
1265 retval = parse_config_file(&idata);
1266 if (retval != PAM_SUCCESS) {
1267 del_polydir_list(idata.polydirs_ptr);
1268 return PAM_SESSION_ERR;
1271 if (idata.polydirs_ptr) {
1272 retval = setup_namespace(&idata, unmnt);
1273 if (idata.flags & PAMNS_DEBUG) {
1275 pam_syslog(idata.pamh, LOG_DEBUG,
1276 "namespace setup failed for pid %d", getpid());
1278 pam_syslog(idata.pamh, LOG_DEBUG,
1279 "namespace setup ok for pid %d", getpid());
1281 } else if (idata.flags & PAMNS_DEBUG)
1282 pam_syslog(idata.pamh, LOG_DEBUG, "Nothing to polyinstantiate");
1284 del_polydir_list(idata.polydirs_ptr);
1290 * Entry point from pam_close_session call.
1292 PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags UNUSED,
1293 int argc, const char **argv)
1296 struct instance_data idata;
1300 /* init instance data */
1302 idata.polydirs_ptr = NULL;
1305 if (is_selinux_enabled())
1306 idata.flags |= PAMNS_SELINUX_ENABLED;
1307 if (ctxt_based_inst_needed())
1308 idata.flags |= PAMNS_CTXT_BASED_INST;
1311 /* Parse arguments. */
1312 for (i = 0; i < argc; i++) {
1313 if (strcmp(argv[i], "debug") == 0)
1314 idata.flags |= PAMNS_DEBUG;
1315 if (strcmp(argv[i], "ignore_config_error") == 0)
1316 idata.flags |= PAMNS_IGN_CONFIG_ERR;
1319 if (idata.flags & PAMNS_DEBUG)
1320 pam_syslog(idata.pamh, LOG_DEBUG, "close_session - start");
1323 * Lookup user and fill struct items
1325 retval = pam_get_item(idata.pamh, PAM_USER, (void*) &user_name );
1326 if ( user_name == NULL || retval != PAM_SUCCESS ) {
1327 pam_syslog(idata.pamh, LOG_ERR, "Error recovering pam user name");
1328 return PAM_SESSION_ERR;
1331 pwd = getpwnam(user_name);
1333 pam_syslog(idata.pamh, LOG_ERR, "user unknown '%s'", user_name);
1334 return PAM_SESSION_ERR;
1338 * Add the user info to the instance data so we can refer to them later.
1341 strncat(idata.user, user_name, sizeof(idata.user));
1342 idata.uid = pwd->pw_uid;
1345 * Parse namespace configuration file which lists directories that
1346 * are polyinstantiated, directories where instance directories are
1347 * created and the method used for polyinstantiation.
1349 retval = parse_config_file(&idata);
1350 if ((retval != PAM_SUCCESS) || !idata.polydirs_ptr) {
1351 del_polydir_list(idata.polydirs_ptr);
1352 return PAM_SESSION_ERR;
1355 if (idata.flags & PAMNS_DEBUG)
1356 pam_syslog(idata.pamh, LOG_DEBUG, "Resetting namespace for pid %d",
1359 retval = orig_namespace(&idata);
1360 if (idata.flags & PAMNS_DEBUG) {
1362 pam_syslog(idata.pamh, LOG_DEBUG,
1363 "resetting namespace failed for pid %d", getpid());
1365 pam_syslog(idata.pamh, LOG_DEBUG,
1366 "resetting namespace ok for pid %d", getpid());
1368 del_polydir_list(idata.polydirs_ptr);
1374 /* static module data */
1376 struct pam_module _pam_namespace_modstruct = {
1381 pam_sm_open_session,
1382 pam_sm_close_session,