user-trap.c 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. #include <signal.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <errno.h>
  6. #include <fcntl.h>
  7. #include <string.h>
  8. #include <stddef.h>
  9. #include <sys/sysmacros.h>
  10. #include <sys/types.h>
  11. #include <sys/wait.h>
  12. #include <sys/socket.h>
  13. #include <sys/stat.h>
  14. #include <sys/mman.h>
  15. #include <sys/syscall.h>
  16. #include <sys/user.h>
  17. #include <sys/ioctl.h>
  18. #include <sys/ptrace.h>
  19. #include <sys/mount.h>
  20. #include <linux/limits.h>
  21. #include <linux/filter.h>
  22. #include <linux/seccomp.h>
  23. #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
  24. static int seccomp(unsigned int op, unsigned int flags, void *args)
  25. {
  26. errno = 0;
  27. return syscall(__NR_seccomp, op, flags, args);
  28. }
  29. static int send_fd(int sock, int fd)
  30. {
  31. struct msghdr msg = {};
  32. struct cmsghdr *cmsg;
  33. char buf[CMSG_SPACE(sizeof(int))] = {0}, c = 'c';
  34. struct iovec io = {
  35. .iov_base = &c,
  36. .iov_len = 1,
  37. };
  38. msg.msg_iov = &io;
  39. msg.msg_iovlen = 1;
  40. msg.msg_control = buf;
  41. msg.msg_controllen = sizeof(buf);
  42. cmsg = CMSG_FIRSTHDR(&msg);
  43. cmsg->cmsg_level = SOL_SOCKET;
  44. cmsg->cmsg_type = SCM_RIGHTS;
  45. cmsg->cmsg_len = CMSG_LEN(sizeof(int));
  46. *((int *)CMSG_DATA(cmsg)) = fd;
  47. msg.msg_controllen = cmsg->cmsg_len;
  48. if (sendmsg(sock, &msg, 0) < 0) {
  49. perror("sendmsg");
  50. return -1;
  51. }
  52. return 0;
  53. }
  54. static int recv_fd(int sock)
  55. {
  56. struct msghdr msg = {};
  57. struct cmsghdr *cmsg;
  58. char buf[CMSG_SPACE(sizeof(int))] = {0}, c = 'c';
  59. struct iovec io = {
  60. .iov_base = &c,
  61. .iov_len = 1,
  62. };
  63. msg.msg_iov = &io;
  64. msg.msg_iovlen = 1;
  65. msg.msg_control = buf;
  66. msg.msg_controllen = sizeof(buf);
  67. if (recvmsg(sock, &msg, 0) < 0) {
  68. perror("recvmsg");
  69. return -1;
  70. }
  71. cmsg = CMSG_FIRSTHDR(&msg);
  72. return *((int *)CMSG_DATA(cmsg));
  73. }
  74. static int user_trap_syscall(int nr, unsigned int flags)
  75. {
  76. struct sock_filter filter[] = {
  77. BPF_STMT(BPF_LD+BPF_W+BPF_ABS,
  78. offsetof(struct seccomp_data, nr)),
  79. BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, nr, 0, 1),
  80. BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_USER_NOTIF),
  81. BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
  82. };
  83. struct sock_fprog prog = {
  84. .len = (unsigned short)ARRAY_SIZE(filter),
  85. .filter = filter,
  86. };
  87. return seccomp(SECCOMP_SET_MODE_FILTER, flags, &prog);
  88. }
  89. static int handle_req(struct seccomp_notif *req,
  90. struct seccomp_notif_resp *resp, int listener)
  91. {
  92. char path[PATH_MAX], source[PATH_MAX], target[PATH_MAX];
  93. int ret = -1, mem;
  94. resp->id = req->id;
  95. resp->error = -EPERM;
  96. resp->val = 0;
  97. if (req->data.nr != __NR_mount) {
  98. fprintf(stderr, "huh? trapped something besides mount? %d\n", req->data.nr);
  99. return -1;
  100. }
  101. /* Only allow bind mounts. */
  102. if (!(req->data.args[3] & MS_BIND))
  103. return 0;
  104. /*
  105. * Ok, let's read the task's memory to see where they wanted their
  106. * mount to go.
  107. */
  108. snprintf(path, sizeof(path), "/proc/%d/mem", req->pid);
  109. mem = open(path, O_RDONLY);
  110. if (mem < 0) {
  111. perror("open mem");
  112. return -1;
  113. }
  114. /*
  115. * Now we avoid a TOCTOU: we referred to a pid by its pid, but since
  116. * the pid that made the syscall may have died, we need to confirm that
  117. * the pid is still valid after we open its /proc/pid/mem file. We can
  118. * ask the listener fd this as follows.
  119. *
  120. * Note that this check should occur *after* any task-specific
  121. * resources are opened, to make sure that the task has not died and
  122. * we're not wrongly reading someone else's state in order to make
  123. * decisions.
  124. */
  125. if (ioctl(listener, SECCOMP_IOCTL_NOTIF_ID_VALID, &req->id) < 0) {
  126. fprintf(stderr, "task died before we could map its memory\n");
  127. goto out;
  128. }
  129. /*
  130. * Phew, we've got the right /proc/pid/mem. Now we can read it. Note
  131. * that to avoid another TOCTOU, we should read all of the pointer args
  132. * before we decide to allow the syscall.
  133. */
  134. if (lseek(mem, req->data.args[0], SEEK_SET) < 0) {
  135. perror("seek");
  136. goto out;
  137. }
  138. ret = read(mem, source, sizeof(source));
  139. if (ret < 0) {
  140. perror("read");
  141. goto out;
  142. }
  143. if (lseek(mem, req->data.args[1], SEEK_SET) < 0) {
  144. perror("seek");
  145. goto out;
  146. }
  147. ret = read(mem, target, sizeof(target));
  148. if (ret < 0) {
  149. perror("read");
  150. goto out;
  151. }
  152. /*
  153. * Our policy is to only allow bind mounts inside /tmp. This isn't very
  154. * interesting, because we could do unprivlieged bind mounts with user
  155. * namespaces already, but you get the idea.
  156. */
  157. if (!strncmp(source, "/tmp/", 5) && !strncmp(target, "/tmp/", 5)) {
  158. if (mount(source, target, NULL, req->data.args[3], NULL) < 0) {
  159. ret = -1;
  160. perror("actual mount");
  161. goto out;
  162. }
  163. resp->error = 0;
  164. }
  165. /* Even if we didn't allow it because of policy, generating the
  166. * response was be a success, because we want to tell the worker EPERM.
  167. */
  168. ret = 0;
  169. out:
  170. close(mem);
  171. return ret;
  172. }
  173. int main(void)
  174. {
  175. int sk_pair[2], ret = 1, status, listener;
  176. pid_t worker = 0 , tracer = 0;
  177. if (socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair) < 0) {
  178. perror("socketpair");
  179. return 1;
  180. }
  181. worker = fork();
  182. if (worker < 0) {
  183. perror("fork");
  184. goto close_pair;
  185. }
  186. if (worker == 0) {
  187. listener = user_trap_syscall(__NR_mount,
  188. SECCOMP_FILTER_FLAG_NEW_LISTENER);
  189. if (listener < 0) {
  190. perror("seccomp");
  191. exit(1);
  192. }
  193. /*
  194. * Drop privileges. We definitely can't mount as uid 1000.
  195. */
  196. if (setuid(1000) < 0) {
  197. perror("setuid");
  198. exit(1);
  199. }
  200. /*
  201. * Send the listener to the parent; also serves as
  202. * synchronization.
  203. */
  204. if (send_fd(sk_pair[1], listener) < 0)
  205. exit(1);
  206. close(listener);
  207. if (mkdir("/tmp/foo", 0755) < 0) {
  208. perror("mkdir");
  209. exit(1);
  210. }
  211. /*
  212. * Try a bad mount just for grins.
  213. */
  214. if (mount("/dev/sda", "/tmp/foo", NULL, 0, NULL) != -1) {
  215. fprintf(stderr, "huh? mounted /dev/sda?\n");
  216. exit(1);
  217. }
  218. if (errno != EPERM) {
  219. perror("bad error from mount");
  220. exit(1);
  221. }
  222. /*
  223. * Ok, we expect this one to succeed.
  224. */
  225. if (mount("/tmp/foo", "/tmp/foo", NULL, MS_BIND, NULL) < 0) {
  226. perror("mount");
  227. exit(1);
  228. }
  229. exit(0);
  230. }
  231. /*
  232. * Get the listener from the child.
  233. */
  234. listener = recv_fd(sk_pair[0]);
  235. if (listener < 0)
  236. goto out_kill;
  237. /*
  238. * Fork a task to handle the requests. This isn't strictly necessary,
  239. * but it makes the particular writing of this sample easier, since we
  240. * can just wait ofr the tracee to exit and kill the tracer.
  241. */
  242. tracer = fork();
  243. if (tracer < 0) {
  244. perror("fork");
  245. goto out_kill;
  246. }
  247. if (tracer == 0) {
  248. struct seccomp_notif *req;
  249. struct seccomp_notif_resp *resp;
  250. struct seccomp_notif_sizes sizes;
  251. if (seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &sizes) < 0) {
  252. perror("seccomp(GET_NOTIF_SIZES)");
  253. goto out_close;
  254. }
  255. req = malloc(sizes.seccomp_notif);
  256. if (!req)
  257. goto out_close;
  258. resp = malloc(sizes.seccomp_notif_resp);
  259. if (!resp)
  260. goto out_req;
  261. memset(resp, 0, sizes.seccomp_notif_resp);
  262. while (1) {
  263. memset(req, 0, sizes.seccomp_notif);
  264. if (ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, req)) {
  265. perror("ioctl recv");
  266. goto out_resp;
  267. }
  268. if (handle_req(req, resp, listener) < 0)
  269. goto out_resp;
  270. /*
  271. * ENOENT here means that the task may have gotten a
  272. * signal and restarted the syscall. It's up to the
  273. * handler to decide what to do in this case, but for
  274. * the sample code, we just ignore it. Probably
  275. * something better should happen, like undoing the
  276. * mount, or keeping track of the args to make sure we
  277. * don't do it again.
  278. */
  279. if (ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, resp) < 0 &&
  280. errno != ENOENT) {
  281. perror("ioctl send");
  282. goto out_resp;
  283. }
  284. }
  285. out_resp:
  286. free(resp);
  287. out_req:
  288. free(req);
  289. out_close:
  290. close(listener);
  291. exit(1);
  292. }
  293. close(listener);
  294. if (waitpid(worker, &status, 0) != worker) {
  295. perror("waitpid");
  296. goto out_kill;
  297. }
  298. if (umount2("/tmp/foo", MNT_DETACH) < 0 && errno != EINVAL) {
  299. perror("umount2");
  300. goto out_kill;
  301. }
  302. if (remove("/tmp/foo") < 0 && errno != ENOENT) {
  303. perror("remove");
  304. exit(1);
  305. }
  306. if (!WIFEXITED(status) || WEXITSTATUS(status)) {
  307. fprintf(stderr, "worker exited nonzero\n");
  308. goto out_kill;
  309. }
  310. ret = 0;
  311. out_kill:
  312. if (tracer > 0)
  313. kill(tracer, SIGKILL);
  314. if (worker > 0)
  315. kill(worker, SIGKILL);
  316. close_pair:
  317. close(sk_pair[0]);
  318. close(sk_pair[1]);
  319. return ret;
  320. }