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