CernVM-FS  2.13.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
cvmfs_suid_helper.cc
Go to the documentation of this file.
1 
10 // clang-format off
11 #include <sys/xattr.h> // NOLINT
12 // clang-format on
13 
14 #include <dirent.h>
15 #include <errno.h>
16 #include <sys/stat.h>
17 #include <sys/wait.h>
18 #include <unistd.h>
19 
20 #include <cstdio>
21 #include <cstdlib>
22 #include <cstring>
23 #include <string>
24 
25 #include "cvmfs_suid_util.h"
26 #include "sanitizer.h"
27 #include "util/platform.h"
28 
29 using namespace std; // NOLINT
30 
31 const char *kSpoolArea = "/var/spool/cvmfs";
32 
36 };
37 
38 static void GetCredentials(uid_t *calling_uid, uid_t *effective_uid) {
39  *calling_uid = getuid();
40  *effective_uid = geteuid();
41 }
42 
43 static void ExecAsRoot(const char *binary, const char *arg1, const char *arg2,
44  const char *arg3, const char *arg4) {
45  char *argv[] = {strdup(binary),
46  arg1 ? strdup(arg1) : NULL,
47  arg2 ? strdup(arg2) : NULL,
48  arg3 ? strdup(arg3) : NULL,
49  arg4 ? strdup(arg4) : NULL,
50  NULL};
51  char *environ[] = {NULL};
52 
53  int retval = setuid(0);
54  if (retval != 0) {
55  fprintf(stderr, "failed to gain root privileges (%d)\n", errno);
56  exit(1);
57  }
58 
59  execve(binary, argv, environ);
60  fprintf(stderr, "failed to run %s... (%d)\n", binary, errno);
61  exit(1);
62 }
63 
64 static void ForkAndExecAsRoot(const char *binary, const char *arg1,
65  const char *arg2, const char *arg3,
66  const char *arg4) {
67  pid_t child = fork();
68  if (child == -1) {
69  fprintf(stderr, "failed to fork %s... (%d)\n", binary, errno);
70  exit(1);
71  } else if (child == 0) {
72  ExecAsRoot(binary, arg1, arg2, arg3, arg4);
73  } else {
74  int wstatus;
75  waitpid(child, &wstatus, 0);
76  if (WIFSIGNALED(wstatus)) {
77  exit(128 + WTERMSIG(wstatus));
78  } else if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus)) {
79  exit(WEXITSTATUS(wstatus));
80  }
81  }
82 }
83 
84 static void Remount(const string &path, const RemountType how) {
85  string remount_option = "remount,";
86  switch (how) {
87  case kRemountRw:
88  remount_option += "rw";
89  break;
90  case kRemountRdonly:
91  remount_option += "ro";
92  break;
93  default:
94  fprintf(stderr, "internal error\n");
95  exit(1);
96  }
97 
98  // Note: the device name "dev" doesn't matter. It won't change from the
99  // "overlayfs_<fqrn>" name used when originally mounting the path. It is
100  // a dummy argument to appease the mount command.
101  ExecAsRoot("/bin/mount", "-o", remount_option.c_str(), "dev", path.c_str());
102 }
103 
104 static void Mount(const string &path) {
105  platform_stat64 info;
106  int retval = platform_stat("/bin/systemctl", &info);
107  if (retval == 0) {
108  string systemd_unit = cvmfs_suid::EscapeSystemdUnit(path);
109  // On newer versions of systemd, the mount unit is based on the fully
110  // resolved path (discovered on Ubuntu 18.04, test 539)
111  if (!cvmfs_suid::PathExists(string("/run/systemd/generator/")
112  + systemd_unit)) {
113  string resolved_path = cvmfs_suid::ResolvePath(path);
114  if (resolved_path.empty()) {
115  fprintf(stderr, "cannot resolve %s\n", path.c_str());
116  exit(1);
117  }
118  systemd_unit = cvmfs_suid::EscapeSystemdUnit(resolved_path);
119  }
120  ForkAndExecAsRoot("/bin/systemctl", "restart", systemd_unit.c_str(), NULL,
121  NULL);
122  ExecAsRoot("/bin/systemctl", "reset-failed", systemd_unit.c_str(), NULL,
123  NULL);
124  } else {
125  ExecAsRoot("/bin/mount", path.c_str(), NULL, NULL, NULL);
126  }
127 }
128 
129 static void Umount(const string &path) {
130  ExecAsRoot("/bin/umount", path.c_str(), NULL, NULL, NULL);
131 }
132 
133 static void LazyUmount(const string &path) {
134  ExecAsRoot("/bin/umount", "-l", path.c_str(), NULL, NULL);
135 }
136 
137 static void KillCvmfs(const string &fqrn) {
138  // prevent exploitation like:
139  // fqrn = ../../../../usr/home/file_with_xattr_user.pid
140  if (fqrn.find("/") != string::npos || fqrn.find("\\") != string::npos) {
141  exit(1);
142  }
143  string pid;
144  const string mountpoint = string(kSpoolArea) + "/" + fqrn + "/rdonly";
145  const bool retval = platform_getxattr(mountpoint.c_str(), "user.pid", &pid);
146  if (!retval || pid.empty())
147  exit(1);
149  if (!pid_sanitizer.IsValid(pid))
150  exit(1);
151  ExecAsRoot("/bin/kill", "-9", pid.c_str(), NULL, NULL);
152 }
153 
155  public:
156  explicit ScopedWorkingDirectory(const string &path)
157  : previous_path_(GetCurrentWorkingDirectory()), directory_handle_(NULL) {
158  ChangeDirectory(path);
159  directory_handle_ = opendir(".");
160  }
161 
163  if (directory_handle_ != NULL) {
164  closedir(directory_handle_);
165  }
166  ChangeDirectory(previous_path_);
167  }
168 
169  operator bool() const { return directory_handle_ != NULL; }
170 
171  struct DirectoryEntry {
173  string name;
174  };
175 
177  platform_dirent64 *dirent;
178  while ((dirent = platform_readdir(directory_handle_)) != NULL
179  && IsDotEntry(dirent)) {
180  }
181  if (dirent == NULL) {
182  return false;
183  }
184 
185  platform_stat64 info;
186  if (platform_lstat(dirent->d_name, &info) != 0) {
187  return false;
188  }
189 
190  entry->is_directory = S_ISDIR(info.st_mode);
191  entry->name = dirent->d_name;
192  return true;
193  }
194 
195  protected:
197  char path[PATH_MAX];
198  const char *cwd = getcwd(path, PATH_MAX);
199  assert(cwd == path);
200  return string(cwd);
201  }
202 
203  void ChangeDirectory(const string &path) {
204  const int retval = chdir(path.c_str());
205  assert(retval == 0);
206  }
207 
208  bool IsDotEntry(const platform_dirent64 *dirent) {
209  return (strcmp(dirent->d_name, ".") == 0)
210  || (strcmp(dirent->d_name, "..") == 0);
211  }
212 
213  private:
214  const string previous_path_;
216 };
217 
218 static bool ClearDirectory(const string &path) {
219  ScopedWorkingDirectory swd(path);
220  if (!swd) {
221  return false;
222  }
223 
224  bool success = true;
226  while (success && swd.NextDirectoryEntry(&dirent)) {
227  success = (dirent.is_directory) ? ClearDirectory(dirent.name)
228  && (rmdir(dirent.name.c_str()) == 0)
229  : (unlink(dirent.name.c_str()) == 0);
230  }
231 
232  return success;
233 }
234 
235 static int CleanupDirectory(const string &path) {
236  if (!ClearDirectory(path)) {
237  fprintf(stderr, "failed to clear %s\n", path.c_str());
238  return 1;
239  }
240  return 0;
241 }
242 
243 static int DoSynchronousScratchCleanup(const string &fqrn) {
244  const string scratch = string(kSpoolArea) + "/" + fqrn + "/scratch/current";
245  return CleanupDirectory(scratch);
246 }
247 
248 static int DoAsynchronousScratchCleanup(const string &fqrn) {
249  const string wastebin = string(kSpoolArea) + "/" + fqrn + "/scratch/wastebin";
250 
251  // double-fork to daemonize the process and redirect I/O to /dev/null
252  pid_t pid;
253  int statloc;
254  if ((pid = fork()) == 0) {
255  int retval = setsid();
256  assert(retval != -1);
257  if ((pid = fork()) == 0) {
258  int null_read = open("/dev/null", O_RDONLY);
259  int null_write = open("/dev/null", O_WRONLY);
260  assert((null_read >= 0) && (null_write >= 0));
261  retval = dup2(null_read, 0);
262  assert(retval == 0);
263  retval = dup2(null_write, 1);
264  assert(retval == 1);
265  retval = dup2(null_write, 2);
266  assert(retval == 2);
267  close(null_read);
268  close(null_write);
269  } else {
270  assert(pid > 0);
271  _exit(0);
272  }
273  } else {
274  assert(pid > 0);
275  waitpid(pid, &statloc, 0);
276  _exit(0);
277  }
278 
279  return CleanupDirectory(wastebin);
280 }
281 
282 static void Usage(const string &exe, FILE *output) {
283  fprintf(output,
284  "Usage: %s lock|open|rw_mount|rw_umount|rdonly_mount|rdonly_umount|"
285  "clear_scratch|clear_scratch_async|kill_cvmfs <fqrn>\n"
286  "Example: %s rw_umount atlas.cern.ch\n"
287  "This binary is typically called by cvmfs_server.\n",
288  exe.c_str(), exe.c_str());
289 }
290 
291 
292 int main(int argc, char *argv[]) {
293  umask(077);
294  int retval;
295 
296  // Figure out real and effective uid
297  uid_t calling_uid, effective_uid, repository_uid;
298  GetCredentials(&calling_uid, &effective_uid);
299  if (effective_uid != 0) {
300  fprintf(stderr, "Needs to run as root\n");
301  return 1;
302  }
303 
304  // Arguments
305  if (argc != 3) {
306  Usage(argv[0], stderr);
307  return 1;
308  }
309  const string command = argv[1];
310  const string fqrn = argv[2];
311 
312  // Verify if repository exists
313  platform_stat64 info;
314  retval = platform_lstat((string(kSpoolArea) + "/" + fqrn).c_str(), &info);
315  if (retval != 0) {
316  fprintf(stderr, "unknown repository: %s\n", fqrn.c_str());
317  return 1;
318  }
319  repository_uid = info.st_uid;
320 
321  // Verify if caller uid matches
322  if ((calling_uid != 0) && (calling_uid != repository_uid)) {
323  fprintf(stderr, "called as %d, repository owned by %d\n", calling_uid,
324  repository_uid);
325  return 1;
326  }
327 
328  if (command == "lock") {
329  Remount("/cvmfs/" + fqrn, kRemountRdonly);
330  } else if (command == "open") {
331  Remount("/cvmfs/" + fqrn, kRemountRw);
332  } else if (command == "kill_cvmfs") {
333  KillCvmfs(fqrn);
334  } else if (command == "rw_mount") {
335  Mount("/cvmfs/" + fqrn);
336  } else if (command == "rw_umount") {
337  Umount("/cvmfs/" + fqrn);
338  } else if (command == "rw_lazy_umount") {
339  LazyUmount("/cvmfs/" + fqrn);
340  } else if (command == "rdonly_mount") {
341  Mount(string(kSpoolArea) + "/" + fqrn + "/rdonly");
342  } else if (command == "rdonly_umount") {
343  Umount(string(kSpoolArea) + "/" + fqrn + "/rdonly");
344  } else if (command == "rdonly_lazy_umount") {
345  LazyUmount(string(kSpoolArea) + "/" + fqrn + "/rdonly");
346  } else if (command == "clear_scratch") {
347  return DoSynchronousScratchCleanup(fqrn);
348  } else if (command == "clear_scratch_async") {
349  return DoAsynchronousScratchCleanup(fqrn);
350  } else {
351  Usage(argv[0], stderr);
352  return 1;
353  }
354 
355  return 0;
356 }
static void Remount(const string &path, const RemountType how)
static void Mount(const string &path)
static void ForkAndExecAsRoot(const char *binary, const char *arg1, const char *arg2, const char *arg3, const char *arg4)
struct stat64 platform_stat64
static void Usage(const char *progname)
static int CleanupDirectory(const string &path)
assert((mem||(size==0))&&"Out Of Memory")
int platform_stat(const char *path, platform_stat64 *buf)
static void GetCredentials(uid_t *calling_uid, uid_t *effective_uid)
bool IsDotEntry(const platform_dirent64 *dirent)
RemountType
static void ExecAsRoot(const char *binary, const char *arg1, const char *arg2, const char *arg3, const char *arg4)
bool IsValid(const std::string &input) const
Definition: sanitizer.cc:112
int main()
Definition: helper_allow.cc:16
const char * kSpoolArea
bool platform_getxattr(const std::string &path, const std::string &name, std::string *value)
string EscapeSystemdUnit(const string &path)
static void Umount(const string &path)
int platform_lstat(const char *path, platform_stat64 *buf)
string ResolvePath(const std::string &path)
ScopedWorkingDirectory(const string &path)
bool PathExists(const std::string &path)
static bool ClearDirectory(const string &path)
static void KillCvmfs(const string &fqrn)
bool NextDirectoryEntry(DirectoryEntry *entry)
void ChangeDirectory(const string &path)
static int DoAsynchronousScratchCleanup(const string &fqrn)
platform_dirent64 * platform_readdir(DIR *dirp)
std::string GetCurrentWorkingDirectory()
Definition: posix.cc:1067
string name
bool is_directory
static int DoSynchronousScratchCleanup(const string &fqrn)
static void LazyUmount(const string &path)
struct dirent64 platform_dirent64