cgroup-network.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #include "libnetdata/libnetdata.h"
  3. #ifdef HAVE_SETNS
  4. #ifndef _GNU_SOURCE
  5. #define _GNU_SOURCE /* See feature_test_macros(7) */
  6. #endif
  7. #include <sched.h>
  8. #endif
  9. char environment_variable2[FILENAME_MAX + 50] = "";
  10. char *environment[] = {
  11. "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin",
  12. environment_variable2,
  13. NULL
  14. };
  15. // ----------------------------------------------------------------------------
  16. // callback required by fatal()
  17. void netdata_cleanup_and_exit(int ret) {
  18. exit(ret);
  19. }
  20. void send_statistics( const char *action, const char *action_result, const char *action_data) {
  21. return;
  22. }
  23. // callbacks required by popen()
  24. void signals_block(void) {};
  25. void signals_unblock(void) {};
  26. void signals_reset(void) {};
  27. // callback required by eval()
  28. int health_variable_lookup(const char *variable, uint32_t hash, struct rrdcalc *rc, calculated_number *result) {
  29. (void)variable;
  30. (void)hash;
  31. (void)rc;
  32. (void)result;
  33. return 0;
  34. };
  35. // required by get_system_cpus()
  36. char *netdata_configured_host_prefix = "";
  37. // ----------------------------------------------------------------------------
  38. struct iface {
  39. const char *device;
  40. uint32_t hash;
  41. unsigned int ifindex;
  42. unsigned int iflink;
  43. struct iface *next;
  44. };
  45. unsigned int read_iface_iflink(const char *prefix, const char *iface) {
  46. if(!prefix) prefix = "";
  47. char filename[FILENAME_MAX + 1];
  48. snprintfz(filename, FILENAME_MAX, "%s/sys/class/net/%s/iflink", prefix, iface);
  49. unsigned long long iflink = 0;
  50. int ret = read_single_number_file(filename, &iflink);
  51. if(ret) error("Cannot read '%s'.", filename);
  52. return (unsigned int)iflink;
  53. }
  54. unsigned int read_iface_ifindex(const char *prefix, const char *iface) {
  55. if(!prefix) prefix = "";
  56. char filename[FILENAME_MAX + 1];
  57. snprintfz(filename, FILENAME_MAX, "%s/sys/class/net/%s/ifindex", prefix, iface);
  58. unsigned long long ifindex = 0;
  59. int ret = read_single_number_file(filename, &ifindex);
  60. if(ret) error("Cannot read '%s'.", filename);
  61. return (unsigned int)ifindex;
  62. }
  63. struct iface *read_proc_net_dev(const char *prefix) {
  64. if(!prefix) prefix = "";
  65. procfile *ff = NULL;
  66. char filename[FILENAME_MAX + 1];
  67. snprintfz(filename, FILENAME_MAX, "%s%s", prefix, (*prefix)?"/proc/1/net/dev":"/proc/net/dev");
  68. ff = procfile_open(filename, " \t,:|", PROCFILE_FLAG_DEFAULT);
  69. if(unlikely(!ff)) {
  70. error("Cannot open file '%s'", filename);
  71. return NULL;
  72. }
  73. ff = procfile_readall(ff);
  74. if(unlikely(!ff)) {
  75. error("Cannot read file '%s'", filename);
  76. return NULL;
  77. }
  78. size_t lines = procfile_lines(ff), l;
  79. struct iface *root = NULL;
  80. for(l = 2; l < lines ;l++) {
  81. if (unlikely(procfile_linewords(ff, l) < 1)) continue;
  82. struct iface *t = callocz(1, sizeof(struct iface));
  83. t->device = strdupz(procfile_lineword(ff, l, 0));
  84. t->hash = simple_hash(t->device);
  85. t->ifindex = read_iface_ifindex(prefix, t->device);
  86. t->iflink = read_iface_iflink(prefix, t->device);
  87. t->next = root;
  88. root = t;
  89. }
  90. procfile_close(ff);
  91. return root;
  92. }
  93. void free_iface(struct iface *iface) {
  94. freez((void *)iface->device);
  95. freez(iface);
  96. }
  97. void free_host_ifaces(struct iface *iface) {
  98. while(iface) {
  99. struct iface *t = iface->next;
  100. free_iface(iface);
  101. iface = t;
  102. }
  103. }
  104. int iface_is_eligible(struct iface *iface) {
  105. if(iface->iflink != iface->ifindex)
  106. return 1;
  107. return 0;
  108. }
  109. int eligible_ifaces(struct iface *root) {
  110. int eligible = 0;
  111. struct iface *t;
  112. for(t = root; t ; t = t->next)
  113. if(iface_is_eligible(t))
  114. eligible++;
  115. return eligible;
  116. }
  117. static void continue_as_child(void) {
  118. pid_t child = fork();
  119. int status;
  120. pid_t ret;
  121. if (child < 0)
  122. error("fork() failed");
  123. /* Only the child returns */
  124. if (child == 0)
  125. return;
  126. for (;;) {
  127. ret = waitpid(child, &status, WUNTRACED);
  128. if ((ret == child) && (WIFSTOPPED(status))) {
  129. /* The child suspended so suspend us as well */
  130. kill(getpid(), SIGSTOP);
  131. kill(child, SIGCONT);
  132. } else {
  133. break;
  134. }
  135. }
  136. /* Return the child's exit code if possible */
  137. if (WIFEXITED(status)) {
  138. exit(WEXITSTATUS(status));
  139. } else if (WIFSIGNALED(status)) {
  140. kill(getpid(), WTERMSIG(status));
  141. }
  142. exit(EXIT_FAILURE);
  143. }
  144. int proc_pid_fd(const char *prefix, const char *ns, pid_t pid) {
  145. if(!prefix) prefix = "";
  146. char filename[FILENAME_MAX + 1];
  147. snprintfz(filename, FILENAME_MAX, "%s/proc/%d/%s", prefix, (int)pid, ns);
  148. int fd = open(filename, O_RDONLY);
  149. if(fd == -1)
  150. error("Cannot open proc_pid_fd() file '%s'", filename);
  151. return fd;
  152. }
  153. static struct ns {
  154. int nstype;
  155. int fd;
  156. int status;
  157. const char *name;
  158. const char *path;
  159. } all_ns[] = {
  160. // { .nstype = CLONE_NEWUSER, .fd = -1, .status = -1, .name = "user", .path = "ns/user" },
  161. // { .nstype = CLONE_NEWCGROUP, .fd = -1, .status = -1, .name = "cgroup", .path = "ns/cgroup" },
  162. // { .nstype = CLONE_NEWIPC, .fd = -1, .status = -1, .name = "ipc", .path = "ns/ipc" },
  163. // { .nstype = CLONE_NEWUTS, .fd = -1, .status = -1, .name = "uts", .path = "ns/uts" },
  164. { .nstype = CLONE_NEWNET, .fd = -1, .status = -1, .name = "network", .path = "ns/net" },
  165. { .nstype = CLONE_NEWPID, .fd = -1, .status = -1, .name = "pid", .path = "ns/pid" },
  166. { .nstype = CLONE_NEWNS, .fd = -1, .status = -1, .name = "mount", .path = "ns/mnt" },
  167. // terminator
  168. { .nstype = 0, .fd = -1, .status = -1, .name = NULL, .path = NULL }
  169. };
  170. int switch_namespace(const char *prefix, pid_t pid) {
  171. if(!prefix) prefix = "";
  172. #ifdef HAVE_SETNS
  173. int i;
  174. for(i = 0; all_ns[i].name ; i++)
  175. all_ns[i].fd = proc_pid_fd(prefix, all_ns[i].path, pid);
  176. int root_fd = proc_pid_fd(prefix, "root", pid);
  177. int cwd_fd = proc_pid_fd(prefix, "cwd", pid);
  178. setgroups(0, NULL);
  179. // 2 passes - found it at nsenter source code
  180. // this is related CLONE_NEWUSER functionality
  181. // This code cannot switch user namespace (it can all the other namespaces)
  182. // Fortunately, we don't need to switch user namespaces.
  183. int pass, errors = 0;
  184. for(pass = 0; pass < 2 ;pass++) {
  185. for(i = 0; all_ns[i].name ; i++) {
  186. if (all_ns[i].fd != -1 && all_ns[i].status == -1) {
  187. if(setns(all_ns[i].fd, all_ns[i].nstype) == -1) {
  188. if(pass == 1) {
  189. all_ns[i].status = 0;
  190. error("Cannot switch to %s namespace of pid %d", all_ns[i].name, (int) pid);
  191. errors++;
  192. }
  193. }
  194. else
  195. all_ns[i].status = 1;
  196. }
  197. }
  198. }
  199. setgroups(0, NULL);
  200. if(root_fd != -1) {
  201. if(fchdir(root_fd) < 0)
  202. error("Cannot fchdir() to pid %d root directory", (int)pid);
  203. if(chroot(".") < 0)
  204. error("Cannot chroot() to pid %d root directory", (int)pid);
  205. close(root_fd);
  206. }
  207. if(cwd_fd != -1) {
  208. if(fchdir(cwd_fd) < 0)
  209. error("Cannot fchdir() to pid %d current working directory", (int)pid);
  210. close(cwd_fd);
  211. }
  212. int do_fork = 0;
  213. for(i = 0; all_ns[i].name ; i++)
  214. if(all_ns[i].fd != -1) {
  215. // CLONE_NEWPID requires a fork() to become effective
  216. if(all_ns[i].nstype == CLONE_NEWPID && all_ns[i].status)
  217. do_fork = 1;
  218. close(all_ns[i].fd);
  219. }
  220. if(do_fork)
  221. continue_as_child();
  222. return 0;
  223. #else
  224. errno = ENOSYS;
  225. error("setns() is missing on this system.");
  226. return 1;
  227. #endif
  228. }
  229. pid_t read_pid_from_cgroup_file(const char *filename) {
  230. int fd = open(filename, procfile_open_flags);
  231. if(fd == -1) {
  232. error("Cannot open pid_from_cgroup() file '%s'.", filename);
  233. return 0;
  234. }
  235. FILE *fp = fdopen(fd, "r");
  236. if(!fp) {
  237. error("Cannot upgrade fd to fp for file '%s'.", filename);
  238. return 0;
  239. }
  240. char buffer[100 + 1];
  241. pid_t pid = 0;
  242. char *s;
  243. while((s = fgets(buffer, 100, fp))) {
  244. buffer[100] = '\0';
  245. pid = atoi(s);
  246. if(pid > 0) break;
  247. }
  248. fclose(fp);
  249. return pid;
  250. }
  251. pid_t read_pid_from_cgroup_files(const char *path) {
  252. char filename[FILENAME_MAX + 1];
  253. snprintfz(filename, FILENAME_MAX, "%s/cgroup.procs", path);
  254. pid_t pid = read_pid_from_cgroup_file(filename);
  255. if(pid > 0) return pid;
  256. snprintfz(filename, FILENAME_MAX, "%s/tasks", path);
  257. return read_pid_from_cgroup_file(filename);
  258. }
  259. pid_t read_pid_from_cgroup(const char *path) {
  260. pid_t pid = read_pid_from_cgroup_files(path);
  261. if (pid > 0) return pid;
  262. DIR *dir = opendir(path);
  263. if (!dir) {
  264. error("cannot read directory '%s'", path);
  265. return 0;
  266. }
  267. struct dirent *de = NULL;
  268. while ((de = readdir(dir))) {
  269. if (de->d_type == DT_DIR
  270. && (
  271. (de->d_name[0] == '.' && de->d_name[1] == '\0')
  272. || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
  273. ))
  274. continue;
  275. if (de->d_type == DT_DIR) {
  276. char filename[FILENAME_MAX + 1];
  277. snprintfz(filename, FILENAME_MAX, "%s/%s", path, de->d_name);
  278. pid = read_pid_from_cgroup(filename);
  279. if(pid > 0) break;
  280. }
  281. }
  282. closedir(dir);
  283. return pid;
  284. }
  285. // ----------------------------------------------------------------------------
  286. // send the result to netdata
  287. struct found_device {
  288. const char *host_device;
  289. const char *guest_device;
  290. uint32_t host_device_hash;
  291. struct found_device *next;
  292. } *detected_devices = NULL;
  293. void add_device(const char *host, const char *guest) {
  294. uint32_t hash = simple_hash(host);
  295. if(guest && (!*guest || strcmp(host, guest) == 0))
  296. guest = NULL;
  297. struct found_device *f;
  298. for(f = detected_devices; f ; f = f->next) {
  299. if(f->host_device_hash == hash && strcmp(host, f->host_device) == 0) {
  300. if(guest && !f->guest_device)
  301. f->guest_device = strdupz(guest);
  302. return;
  303. }
  304. }
  305. f = mallocz(sizeof(struct found_device));
  306. f->host_device = strdupz(host);
  307. f->host_device_hash = hash;
  308. f->guest_device = (guest)?strdupz(guest):NULL;
  309. f->next = detected_devices;
  310. detected_devices = f;
  311. }
  312. int send_devices(void) {
  313. int found = 0;
  314. struct found_device *f;
  315. for(f = detected_devices; f ; f = f->next) {
  316. found++;
  317. printf("%s %s\n", f->host_device, (f->guest_device)?f->guest_device:f->host_device);
  318. }
  319. return found;
  320. }
  321. // ----------------------------------------------------------------------------
  322. // this function should be called only **ONCE**
  323. // also it has to be the **LAST** to be called
  324. // since it switches namespaces, so after this call, everything is different!
  325. void detect_veth_interfaces(pid_t pid) {
  326. struct iface *host = NULL, *cgroup = NULL, *h, *c;
  327. host = read_proc_net_dev(netdata_configured_host_prefix);
  328. if(!host) {
  329. errno = 0;
  330. error("cannot read host interface list.");
  331. goto cleanup;
  332. }
  333. if(!eligible_ifaces(host)) {
  334. errno = 0;
  335. error("there are no double-linked host interfaces available.");
  336. goto cleanup;
  337. }
  338. if(switch_namespace(netdata_configured_host_prefix, pid)) {
  339. errno = 0;
  340. error("cannot switch to the namespace of pid %u", (unsigned int) pid);
  341. goto cleanup;
  342. }
  343. cgroup = read_proc_net_dev(NULL);
  344. if(!cgroup) {
  345. errno = 0;
  346. error("cannot read cgroup interface list.");
  347. goto cleanup;
  348. }
  349. if(!eligible_ifaces(cgroup)) {
  350. errno = 0;
  351. error("there are not double-linked cgroup interfaces available.");
  352. goto cleanup;
  353. }
  354. for(h = host; h ; h = h->next) {
  355. if(iface_is_eligible(h)) {
  356. for (c = cgroup; c; c = c->next) {
  357. if(iface_is_eligible(c) && h->ifindex == c->iflink && h->iflink == c->ifindex) {
  358. add_device(h->device, c->device);
  359. }
  360. }
  361. }
  362. }
  363. cleanup:
  364. free_host_ifaces(cgroup);
  365. free_host_ifaces(host);
  366. }
  367. // ----------------------------------------------------------------------------
  368. // call the external helper
  369. #define CGROUP_NETWORK_INTERFACE_MAX_LINE 2048
  370. void call_the_helper(pid_t pid, const char *cgroup) {
  371. if(setresuid(0, 0, 0) == -1)
  372. error("setresuid(0, 0, 0) failed.");
  373. char command[CGROUP_NETWORK_INTERFACE_MAX_LINE + 1];
  374. if(cgroup)
  375. snprintfz(command, CGROUP_NETWORK_INTERFACE_MAX_LINE, "exec " PLUGINS_DIR "/cgroup-network-helper.sh --cgroup '%s'", cgroup);
  376. else
  377. snprintfz(command, CGROUP_NETWORK_INTERFACE_MAX_LINE, "exec " PLUGINS_DIR "/cgroup-network-helper.sh --pid %d", pid);
  378. info("running: %s", command);
  379. pid_t cgroup_pid;
  380. FILE *fp = mypopene(command, &cgroup_pid, environment);
  381. if(fp) {
  382. char buffer[CGROUP_NETWORK_INTERFACE_MAX_LINE + 1];
  383. char *s;
  384. while((s = fgets(buffer, CGROUP_NETWORK_INTERFACE_MAX_LINE, fp))) {
  385. trim(s);
  386. if(*s && *s != '\n') {
  387. char *t = s;
  388. while(*t && *t != ' ') t++;
  389. if(*t == ' ') {
  390. *t = '\0';
  391. t++;
  392. }
  393. if(!*s || !*t) continue;
  394. add_device(s, t);
  395. }
  396. }
  397. mypclose(fp, cgroup_pid);
  398. }
  399. else
  400. error("cannot execute cgroup-network helper script: %s", command);
  401. }
  402. int is_valid_path_symbol(char c) {
  403. switch(c) {
  404. case '/': // path separators
  405. case '\\': // needed for virsh domains \x2d1\x2dname
  406. case ' ': // space
  407. case '-': // hyphen
  408. case '_': // underscore
  409. case '.': // dot
  410. case ',': // comma
  411. return 1;
  412. default:
  413. return 0;
  414. }
  415. }
  416. // we will pass this path a shell script running as root
  417. // so, we need to make sure the path will be valid
  418. // and will not include anything that could allow
  419. // the caller use shell expansion for gaining escalated
  420. // privileges.
  421. int verify_path(const char *path) {
  422. struct stat sb;
  423. char c;
  424. const char *s = path;
  425. while((c = *s++)) {
  426. if(!( isalnum(c) || is_valid_path_symbol(c) )) {
  427. error("invalid character in path '%s'", path);
  428. return -1;
  429. }
  430. }
  431. if(strstr(path, "\\") && !strstr(path, "\\x")) {
  432. error("invalid escape sequence in path '%s'", path);
  433. return 1;
  434. }
  435. if(strstr(path, "/../")) {
  436. error("invalid parent path sequence detected in '%s'", path);
  437. return 1;
  438. }
  439. if(path[0] != '/') {
  440. error("only absolute path names are supported - invalid path '%s'", path);
  441. return -1;
  442. }
  443. if (stat(path, &sb) == -1) {
  444. error("cannot stat() path '%s'", path);
  445. return -1;
  446. }
  447. if((sb.st_mode & S_IFMT) != S_IFDIR) {
  448. error("path '%s' is not a directory", path);
  449. return -1;
  450. }
  451. return 0;
  452. }
  453. /*
  454. char *fix_path_variable(void) {
  455. const char *path = getenv("PATH");
  456. if(!path || !*path) return 0;
  457. char *p = strdupz(path);
  458. char *safe_path = callocz(1, strlen(p) + strlen("PATH=") + 1);
  459. strcpy(safe_path, "PATH=");
  460. int added = 0;
  461. char *ptr = p;
  462. while(ptr && *ptr) {
  463. char *s = strsep(&ptr, ":");
  464. if(s && *s) {
  465. if(verify_path(s) == -1) {
  466. error("the PATH variable includes an invalid path '%s' - removed it.", s);
  467. }
  468. else {
  469. info("the PATH variable includes a valid path '%s'.", s);
  470. if(added) strcat(safe_path, ":");
  471. strcat(safe_path, s);
  472. added++;
  473. }
  474. }
  475. }
  476. info("unsafe PATH: '%s'.", path);
  477. info(" safe PATH: '%s'.", safe_path);
  478. freez(p);
  479. return safe_path;
  480. }
  481. */
  482. // ----------------------------------------------------------------------------
  483. // main
  484. void usage(void) {
  485. fprintf(stderr, "%s [ -p PID | --pid PID | --cgroup /path/to/cgroup ]\n", program_name);
  486. exit(1);
  487. }
  488. int main(int argc, char **argv) {
  489. pid_t pid = 0;
  490. program_name = argv[0];
  491. program_version = VERSION;
  492. error_log_syslog = 0;
  493. // since cgroup-network runs as root, prevent it from opening symbolic links
  494. procfile_open_flags = O_RDONLY|O_NOFOLLOW;
  495. // ------------------------------------------------------------------------
  496. // make sure NETDATA_HOST_PREFIX is safe
  497. netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
  498. if(verify_netdata_host_prefix() == -1) exit(1);
  499. if(netdata_configured_host_prefix[0] != '\0' && verify_path(netdata_configured_host_prefix) == -1)
  500. fatal("invalid NETDATA_HOST_PREFIX '%s'", netdata_configured_host_prefix);
  501. // ------------------------------------------------------------------------
  502. // build a safe environment for our script
  503. // the first environment variable is a fixed PATH=
  504. snprintfz(environment_variable2, sizeof(environment_variable2) - 1, "NETDATA_HOST_PREFIX=%s", netdata_configured_host_prefix);
  505. // ------------------------------------------------------------------------
  506. if(argc == 2 && (!strcmp(argv[1], "version") || !strcmp(argv[1], "-version") || !strcmp(argv[1], "--version") || !strcmp(argv[1], "-v") || !strcmp(argv[1], "-V"))) {
  507. fprintf(stderr, "cgroup-network %s\n", VERSION);
  508. exit(0);
  509. }
  510. if(argc != 3)
  511. usage();
  512. if(!strcmp(argv[1], "-p") || !strcmp(argv[1], "--pid")) {
  513. pid = atoi(argv[2]);
  514. if(pid <= 0) {
  515. errno = 0;
  516. error("Invalid pid %d given", (int) pid);
  517. return 2;
  518. }
  519. call_the_helper(pid, NULL);
  520. }
  521. else if(!strcmp(argv[1], "--cgroup")) {
  522. char *cgroup = argv[2];
  523. if(verify_path(cgroup) == -1)
  524. fatal("cgroup '%s' does not exist or is not valid.", cgroup);
  525. pid = read_pid_from_cgroup(cgroup);
  526. call_the_helper(pid, cgroup);
  527. if(pid <= 0 && !detected_devices) {
  528. errno = 0;
  529. error("Cannot find a cgroup PID from cgroup '%s'", cgroup);
  530. }
  531. }
  532. else
  533. usage();
  534. if(pid > 0)
  535. detect_veth_interfaces(pid);
  536. int found = send_devices();
  537. if(found <= 0) return 1;
  538. return 0;
  539. }