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