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