github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/cmd/libsnap-confine-private/locking.c (about)

     1  /*
     2   * Copyright (C) 2017-2018 Canonical Ltd
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License version 3 as
     6   * published by the Free Software Foundation.
     7   *
     8   * This program is distributed in the hope that it will be useful,
     9   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11   * GNU General Public License for more details.
    12   *
    13   * You should have received a copy of the GNU General Public License
    14   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15   *
    16   */
    17  
    18  #ifdef HAVE_CONFIG_H
    19  #include "config.h"
    20  #endif
    21  
    22  #include "locking.h"
    23  
    24  #include <errno.h>
    25  #include <fcntl.h>
    26  #include <signal.h>
    27  #include <stdarg.h>
    28  #include <sys/file.h>
    29  #include <sys/stat.h>
    30  #include <sys/types.h>
    31  #include <unistd.h>
    32  
    33  #include "../libsnap-confine-private/cleanup-funcs.h"
    34  #include "../libsnap-confine-private/string-utils.h"
    35  #include "../libsnap-confine-private/utils.h"
    36  
    37  // SANITY_TIMEOUT is the timeout in seconds that is used when
    38  // "sc_enable_sanity_timeout()" is called
    39  static const int SANITY_TIMEOUT = 30;
    40  
    41  /**
    42   * Flag indicating that a sanity timeout has expired.
    43   **/
    44  static volatile sig_atomic_t sanity_timeout_expired = 0;
    45  
    46  /**
    47   * Signal handler for SIGALRM that sets sanity_timeout_expired flag to 1.
    48   **/
    49  static void sc_SIGALRM_handler(int signum)
    50  {
    51  	sanity_timeout_expired = 1;
    52  }
    53  
    54  void sc_enable_sanity_timeout(void)
    55  {
    56  	sanity_timeout_expired = 0;
    57  	struct sigaction act = {.sa_handler = sc_SIGALRM_handler };
    58  	if (sigemptyset(&act.sa_mask) < 0) {
    59  		die("cannot initialize POSIX signal set");
    60  	}
    61  	// NOTE: we are using sigaction so that we can explicitly control signal
    62  	// flags and *not* pass the SA_RESTART flag. The intent is so that any
    63  	// system call we may be sleeping on to gets interrupted.
    64  	act.sa_flags = 0;
    65  	if (sigaction(SIGALRM, &act, NULL) < 0) {
    66  		die("cannot install signal handler for SIGALRM");
    67  	}
    68  	alarm(SANITY_TIMEOUT);
    69  	debug("sanity timeout initialized and set for %i seconds",
    70  	      SANITY_TIMEOUT);
    71  }
    72  
    73  void sc_disable_sanity_timeout(void)
    74  {
    75  	if (sanity_timeout_expired) {
    76  		die("sanity timeout expired");
    77  	}
    78  	alarm(0);
    79  	struct sigaction act = {.sa_handler = SIG_DFL };
    80  	if (sigemptyset(&act.sa_mask) < 0) {
    81  		die("cannot initialize POSIX signal set");
    82  	}
    83  	if (sigaction(SIGALRM, &act, NULL) < 0) {
    84  		die("cannot uninstall signal handler for SIGALRM");
    85  	}
    86  	debug("sanity timeout reset and disabled");
    87  }
    88  
    89  #define SC_LOCK_DIR "/run/snapd/lock"
    90  
    91  static const char *sc_lock_dir = SC_LOCK_DIR;
    92  
    93  static int get_lock_directory(void)
    94  {
    95  	// Create (if required) and open the lock directory.
    96  	debug("creating lock directory %s (if missing)", sc_lock_dir);
    97  	sc_identity old = sc_set_effective_identity(sc_root_group_identity());
    98  	if (sc_nonfatal_mkpath(sc_lock_dir, 0755) < 0) {
    99  		die("cannot create lock directory %s", sc_lock_dir);
   100  	}
   101  	debug("opening lock directory %s", sc_lock_dir);
   102  	int dir_fd =
   103  	    open(sc_lock_dir, O_DIRECTORY | O_PATH | O_CLOEXEC | O_NOFOLLOW);
   104  	(void)sc_set_effective_identity(old);
   105  	if (dir_fd < 0) {
   106  		die("cannot open lock directory");
   107  	}
   108  	return dir_fd;
   109  }
   110  
   111  static void get_lock_name(char *lock_fname, size_t size, const char *scope,
   112  			  uid_t uid)
   113  {
   114  	if (uid == 0) {
   115  		// The root user doesn't have a per-user mount namespace.
   116  		// Doing so would be confusing for services which use $SNAP_DATA
   117  		// as home, and not in $SNAP_USER_DATA.
   118  		sc_must_snprintf(lock_fname, size, "%s.lock", scope ? : "");
   119  	} else {
   120  		sc_must_snprintf(lock_fname, size, "%s.%d.lock",
   121  				 scope ? : "", uid);
   122  	}
   123  }
   124  
   125  static int open_lock(const char *scope, uid_t uid)
   126  {
   127  	int dir_fd SC_CLEANUP(sc_cleanup_close) = -1;
   128  	char lock_fname[PATH_MAX] = { 0 };
   129  	int lock_fd;
   130  
   131  	dir_fd = get_lock_directory();
   132  	get_lock_name(lock_fname, sizeof lock_fname, scope, uid);
   133  
   134  	// Open the lock file and acquire an exclusive lock.
   135  	debug("opening lock file: %s/%s", sc_lock_dir, lock_fname);
   136  	sc_identity old = sc_set_effective_identity(sc_root_group_identity());
   137  	lock_fd = openat(dir_fd, lock_fname,
   138  			 O_CREAT | O_RDWR | O_CLOEXEC | O_NOFOLLOW, 0600);
   139  	(void)sc_set_effective_identity(old);
   140  	if (lock_fd < 0) {
   141  		die("cannot open lock file: %s/%s", sc_lock_dir, lock_fname);
   142  	}
   143  	return lock_fd;
   144  }
   145  
   146  static int sc_lock_generic(const char *scope, uid_t uid)
   147  {
   148  	int lock_fd = open_lock(scope, uid);
   149  	sc_enable_sanity_timeout();
   150  	debug("acquiring exclusive lock (scope %s, uid %d)",
   151  	      scope ? : "(global)", uid);
   152  	if (flock(lock_fd, LOCK_EX) < 0) {
   153  		sc_disable_sanity_timeout();
   154  		close(lock_fd);
   155  		die("cannot acquire exclusive lock (scope %s, uid %d)",
   156  		    scope ? : "(global)", uid);
   157  	} else {
   158  		sc_disable_sanity_timeout();
   159  	}
   160  	return lock_fd;
   161  }
   162  
   163  int sc_lock_global(void)
   164  {
   165  	return sc_lock_generic(NULL, 0);
   166  }
   167  
   168  int sc_lock_snap(const char *snap_name)
   169  {
   170  	return sc_lock_generic(snap_name, 0);
   171  }
   172  
   173  void sc_verify_snap_lock(const char *snap_name)
   174  {
   175  	int lock_fd, retval;
   176  
   177  	lock_fd = open_lock(snap_name, 0);
   178  	debug("trying to verify whether exclusive lock over snap %s is held",
   179  	      snap_name);
   180  	retval = flock(lock_fd, LOCK_EX | LOCK_NB);
   181  	if (retval == 0) {
   182  		/* We managed to grab the lock, the lock was not held! */
   183  		flock(lock_fd, LOCK_UN);
   184  		close(lock_fd);
   185  		errno = 0;
   186  		die("unexpectedly managed to acquire exclusive lock over snap %s", snap_name);
   187  	}
   188  	if (retval < 0 && errno != EWOULDBLOCK) {
   189  		die("cannot verify exclusive lock over snap %s", snap_name);
   190  	}
   191  	/* We tried but failed to grab the lock because the file is already locked.
   192  	 * Good, this is what we expected. */
   193  }
   194  
   195  int sc_lock_snap_user(const char *snap_name, uid_t uid)
   196  {
   197  	return sc_lock_generic(snap_name, uid);
   198  }
   199  
   200  void sc_unlock(int lock_fd)
   201  {
   202  	// Release the lock and finish.
   203  	debug("releasing lock %d", lock_fd);
   204  	if (flock(lock_fd, LOCK_UN) < 0) {
   205  		die("cannot release lock %d", lock_fd);
   206  	}
   207  	close(lock_fd);
   208  }