logo

oasis

Own branch of Oasis Linux (upstream: <https://git.sr.ht/~mcf/oasis/>) git clone https://anongit.hacktivis.me/git/oasis.git

applyperms.c (8708B)


  1. /*
  2. See LICENSE file for copyright and license details.
  3. This program is meant to be run by a git hook to fix the permissions of files
  4. based on a .perms file in the repository
  5. It can also be run with the -d flag on any directory containing .perms in order
  6. to apply the permissions specified by that file.
  7. Security considerations:
  8. If the repository previously contained a world or group readable directory which
  9. has become secret, the names of the new files in that directory will become
  10. temporarily visible because git checks out the files before this program is run.
  11. */
  12. #define _POSIX_C_SOURCE 200809L
  13. #include <errno.h>
  14. #include <fcntl.h>
  15. #include <libgen.h>
  16. #include <spawn.h>
  17. #include <stdarg.h>
  18. #include <stdbool.h>
  19. #include <stdio.h>
  20. #include <stdlib.h>
  21. #include <stdnoreturn.h>
  22. #include <string.h>
  23. #include <sys/stat.h>
  24. #include <sys/wait.h>
  25. #include <unistd.h>
  26. #define PERMS_FILE ".perms"
  27. struct perm {
  28. char *name;
  29. mode_t mode;
  30. bool attempted; /* whether we attempted to set permissions for this file */
  31. bool delete; /* marked for directories that only appear in old .perms */
  32. };
  33. struct special {
  34. struct perm *perms;
  35. int len;
  36. };
  37. extern char **environ;
  38. static char *prog;
  39. static dev_t rootdev;
  40. static int rootfd = AT_FDCWD;
  41. static struct special oldsp, newsp;
  42. static int status;
  43. static void
  44. verror(char *fmt, va_list ap)
  45. {
  46. status = 1;
  47. vfprintf(stderr, fmt, ap);
  48. if (fmt[0] && fmt[strlen(fmt)-1] == ':')
  49. fprintf(stderr, " %s", strerror(errno));
  50. fputc('\n', stderr);
  51. }
  52. static void
  53. error(char *fmt, ...)
  54. {
  55. va_list ap;
  56. va_start(ap, fmt);
  57. verror(fmt, ap);
  58. va_end(ap);
  59. }
  60. static noreturn void
  61. die(char *fmt, ...)
  62. {
  63. va_list ap;
  64. va_start(ap, fmt);
  65. verror(fmt, ap);
  66. va_end(ap);
  67. exit(1);
  68. }
  69. static FILE *
  70. spawn(char **argv, pid_t *pid)
  71. {
  72. FILE *f;
  73. int fd[2];
  74. posix_spawn_file_actions_t actions;
  75. if (pipe(fd) < 0)
  76. die("pipe:");
  77. f = fdopen(fd[0], "r");
  78. if (!f)
  79. die("fdopen:");
  80. if ((errno = posix_spawn_file_actions_init(&actions)) > 0)
  81. die("posix_spawn_file_actions_init:");
  82. if ((errno = posix_spawn_file_actions_addclose(&actions, fd[0])) > 0)
  83. die("posix_spawn_file_actions_adddup2:");
  84. if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 1)) > 0)
  85. die("posix_spawn_file_actions_adddup2:");
  86. if ((errno = posix_spawnp(pid, argv[0], &actions, NULL, argv, environ)) > 0)
  87. die("posix_spawnp %s:", argv[0]);
  88. posix_spawn_file_actions_destroy(&actions);
  89. close(fd[1]);
  90. return f;
  91. }
  92. static void
  93. readspecial(struct special *sp, FILE *f)
  94. {
  95. char *line = NULL, *s, *mode;
  96. size_t size = 0;
  97. ssize_t n;
  98. while ((n = getline(&line, &size, f)) >= 0) {
  99. if (line[n-1] == '\n')
  100. line[--n] = '\0';
  101. mode = s = line;
  102. s = strchr(s, ' ');
  103. if (!s || s == mode)
  104. die("malformed permissions file: %s", PERMS_FILE);
  105. *s++ = '\0';
  106. sp->perms = realloc(sp->perms, (sp->len + 1) * sizeof(*sp->perms));
  107. if (!sp->perms)
  108. die("realloc:");
  109. sp->perms[sp->len] = (struct perm){
  110. .name = strdup(s),
  111. .mode = strtoul(mode, &s, 8),
  112. };
  113. if (!sp->perms[sp->len].name)
  114. die("strdup:");
  115. if (*s)
  116. die("invalid mode: %s", mode);
  117. ++sp->len;
  118. }
  119. }
  120. static void
  121. gitspecial(struct special *sp, const char *rev)
  122. {
  123. char object[41 + sizeof(PERMS_FILE)];
  124. char *argv[] = {"git", "show", object, 0};
  125. FILE *f;
  126. pid_t pid;
  127. int st, n;
  128. n = snprintf(object, sizeof(object), "%s:%s", rev, PERMS_FILE);
  129. if (n < 0 || n >= (int)sizeof(object))
  130. die("revision is too large: %s", rev);
  131. f = spawn(argv, &pid);
  132. readspecial(sp, f);
  133. fclose(f);
  134. if (waitpid(pid, &st, 0) < 0)
  135. die("waitpid:");
  136. if (!(WIFEXITED(st) && WEXITSTATUS(st) == 0))
  137. die("child process failed");
  138. }
  139. static int
  140. chmod_v(const char *path, mode_t mode)
  141. {
  142. printf("chmod(\"%s\", %#o)\n", path, mode);
  143. return fchmodat(rootfd, path, mode, 0);
  144. }
  145. static int
  146. mkdir_v(const char *path, mode_t mode)
  147. {
  148. printf("mkdir(\"%s\", %#o)\n", path, mode);
  149. return mkdirat(rootfd, path, mode);
  150. }
  151. static int
  152. rmdir_v(const char *path)
  153. {
  154. printf("rmdir(\"%s\")\n", path);
  155. return unlinkat(rootfd, path, AT_REMOVEDIR);
  156. }
  157. static int
  158. defperm(const char *name)
  159. {
  160. struct stat st;
  161. mode_t mode;
  162. if (fstatat(rootfd, name, &st, AT_SYMLINK_NOFOLLOW) < 0)
  163. return -1;
  164. if (st.st_dev != rootdev) {
  165. errno = EXDEV;
  166. return -1;
  167. }
  168. switch (st.st_mode & S_IFMT) {
  169. case S_IFREG:
  170. mode = st.st_mode & S_IXUSR ? 0755 : 0644;
  171. break;
  172. case S_IFDIR:
  173. mode = 0755;
  174. break;
  175. case S_IFLNK:
  176. return 0;
  177. default:
  178. errno = EINVAL;
  179. return -1;
  180. }
  181. if ((st.st_mode & ~S_IFMT) == mode)
  182. return 0;
  183. return chmod_v(name, mode);
  184. }
  185. static int
  186. specialperm(struct perm *p)
  187. {
  188. struct stat st;
  189. if (p->attempted)
  190. return 0;
  191. p->attempted = true;
  192. if (fstatat(rootfd, p->name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
  193. if (errno != ENOENT || !S_ISDIR(p->mode))
  194. return -1;
  195. return mkdir_v(p->name, p->mode & ~S_IFMT);
  196. }
  197. if (st.st_dev != rootdev) {
  198. errno = EXDEV;
  199. return -1;
  200. }
  201. if (st.st_mode == p->mode)
  202. return 0;
  203. if ((st.st_mode & S_IFMT) != (p->mode & S_IFMT)) {
  204. errno = EINVAL;
  205. return -1;
  206. }
  207. return chmod_v(p->name, p->mode & ~S_IFMT);
  208. }
  209. static void
  210. specialperms(void)
  211. {
  212. int i = 0, j = 0, n;
  213. while (i < oldsp.len || j < newsp.len) {
  214. if (i == oldsp.len)
  215. n = 1;
  216. else if (j == newsp.len)
  217. n = -1;
  218. else
  219. n = strcmp(oldsp.perms[i].name, newsp.perms[j].name);
  220. if (n >= 0) {
  221. if (specialperm(&newsp.perms[j]) < 0 && errno != EXDEV)
  222. error("specialperm:");
  223. ++j;
  224. if (n == 0)
  225. ++i;
  226. continue;
  227. }
  228. if ((oldsp.perms[i].mode & S_IFMT) == S_IFDIR) {
  229. oldsp.perms[i].delete = true;
  230. } else if (defperm(oldsp.perms[i].name) < 0) {
  231. switch (errno) {
  232. case ENOENT:
  233. case EXDEV:
  234. break;
  235. default:
  236. error("defperm:");
  237. }
  238. }
  239. ++i;
  240. }
  241. /* delete directories in reverse order */
  242. while (i > 0) {
  243. --i;
  244. if (oldsp.perms[i].delete && rmdir_v(oldsp.perms[i].name) < 0) {
  245. switch (errno) {
  246. case ENOENT:
  247. case ENOTEMPTY:
  248. break;
  249. default:
  250. error("rmdir:");
  251. }
  252. }
  253. }
  254. }
  255. static int
  256. setperm(const char *name)
  257. {
  258. int i;
  259. for (i = 0; i < newsp.len; ++i) {
  260. if (strcmp(name, newsp.perms[i].name) == 0)
  261. return specialperm(&newsp.perms[i]);
  262. }
  263. return defperm(name);
  264. }
  265. static void
  266. setroot(const char *root)
  267. {
  268. struct stat st;
  269. rootfd = open(root, O_RDONLY);
  270. if (rootfd < 0)
  271. die("open %s:", root);
  272. if (fstat(rootfd, &st) < 0)
  273. die("fstat:", root);
  274. rootdev = st.st_dev;
  275. }
  276. static void
  277. gitupdate(char *old, char *new)
  278. {
  279. char *argv_diff[] = {"git", "diff", "--name-only", "-z", old, new, 0};
  280. char *argv_new[] = {"git", "ls-tree", "--name-only", "--full-tree", "-z", "-r", new, 0};
  281. FILE *f;
  282. pid_t pid;
  283. struct {
  284. char *buf;
  285. size_t size;
  286. } lines[2] = {0};
  287. ssize_t n;
  288. int cur = 0, st;
  289. char *root, *path, *diff, *s;
  290. root = getenv("GIT_WORK_TREE");
  291. setroot(root ? root : ".");
  292. if (old)
  293. gitspecial(&oldsp, old);
  294. gitspecial(&newsp, new);
  295. f = spawn(old ? argv_diff : argv_new, &pid);
  296. umask(0);
  297. while ((n = getdelim(&lines[cur].buf, &lines[cur].size, '\0', f)) >= 0) {
  298. path = lines[cur].buf;
  299. if (strcmp(path, PERMS_FILE) == 0) {
  300. specialperms();
  301. continue;
  302. }
  303. if (setperm(path) < 0) switch (errno) {
  304. case ENOENT:
  305. continue;
  306. case EXDEV:
  307. break;
  308. default:
  309. error("setperm %s:", path);
  310. }
  311. /* find the first difference from the previous path */
  312. diff = path;
  313. if (lines[!cur].buf)
  314. for (s = lines[!cur].buf; *s && *s == *diff; ++s, ++diff);
  315. /* set permissions on each parent directory after that difference */
  316. for (s = path + n; s >= diff; --s) {
  317. if (*s != '/')
  318. continue;
  319. *s = '\0';
  320. if (setperm(path) < 0)
  321. error("setperm %s:", path);
  322. *s = '/';
  323. }
  324. cur = !cur;
  325. }
  326. fclose(f);
  327. if (waitpid(pid, &st, 0) < 0)
  328. die("waitpid:");
  329. if (!(WIFEXITED(st) && WEXITSTATUS(st) == 0))
  330. die("child process failed");
  331. }
  332. static void
  333. applyspecial(void)
  334. {
  335. int fd;
  336. FILE *f;
  337. fd = openat(rootfd, ".perms", O_RDONLY);
  338. if (fd < 0)
  339. die("open .perms:");
  340. f = fdopen(fd, "r");
  341. if (!f)
  342. die("fdopen:");
  343. readspecial(&newsp, f);
  344. fclose(f);
  345. umask(0);
  346. specialperms();
  347. }
  348. static void
  349. usage(void)
  350. {
  351. fprintf(stderr, "usage: %s [[old] new] | %s -d dir\n", prog, prog);
  352. exit(2);
  353. }
  354. int
  355. main(int argc, char *argv[])
  356. {
  357. int dflag = 0;
  358. char *old, *new;
  359. prog = basename(argv[0]);
  360. for (++argv, --argc; argc && (*argv)[0] == '-' && (*argv)[1]; ++argv, --argc) {
  361. switch ((*argv)[1]) {
  362. case 'd':
  363. if (!*++argv)
  364. usage();
  365. --argc;
  366. setroot(*argv);
  367. dflag = 1;
  368. break;
  369. default:
  370. usage();
  371. }
  372. }
  373. if (dflag) {
  374. if (argc)
  375. usage();
  376. applyspecial();
  377. return status;
  378. }
  379. switch (argc) {
  380. case 0:
  381. old = NULL;
  382. new = "HEAD";
  383. break;
  384. case 1:
  385. old = NULL;
  386. new = argv[0];
  387. break;
  388. case 2:
  389. old = argv[0];
  390. new = argv[1];
  391. break;
  392. default:
  393. usage();
  394. }
  395. gitupdate(old, new);
  396. return status;
  397. }