10/4
This commit is contained in:
1 parent
c432274936
commit
ee4bcc3b77
27 files changed
+2719
-589
No files matched your search
+65
-1
@@ -20,6 +20,64 @@ typedef struct
|
||||
}
|
||||
dir_t;
|
||||
|
||||
/*
|
||||
* On-disk layout. Each scope keeps everything it ever creates, renames
|
||||
* or locks under ONE base directory it owns:
|
||||
*
|
||||
* /glacier/usr/<uid>/{index,store,links,stage,old,lock} owned by uid, 0700
|
||||
* /glacier/sys/{index,store,links,stage,old,lock} root
|
||||
*
|
||||
* /glacier/usr itself is root-owned and not group/world-writable, so a
|
||||
* user can never create (or squat) another uid's base; root creates
|
||||
* each one (gl_init_user, via `gstore -n` directly or through doas).
|
||||
* Every transaction step — stage, old/, lock, the commit renames — then
|
||||
* happens inside a directory the acting user owns, which is what lets
|
||||
* an unprivileged user run their own transactions at all.
|
||||
*/
|
||||
#define GL_USR_ROOT "/glacier/usr"
|
||||
#define GL_SYS_ROOT "/glacier/sys"
|
||||
|
||||
static inline void
|
||||
gl_usr_base(uid_t uid, char *out, size_t outsz)
|
||||
{
|
||||
snprintf(out, outsz, GL_USR_ROOT "/%u", (unsigned)uid);
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
GL_USR_BASE_OK = 0,
|
||||
GL_USR_BASE_MISSING, /* no /glacier/usr/<uid> yet: run gstore -n */
|
||||
GL_USR_BASE_UNSAFE_ROOT, /* /glacier/usr not root-owned, or group/world-writable */
|
||||
GL_USR_BASE_UNSAFE /* not a real dir, not owned by uid, or not 0700-ish */
|
||||
} gl_usr_base_status_t;
|
||||
|
||||
/* Refuse to operate on a user base we didn't hand out. lstat(), never
|
||||
* stat(): a symlink in place of /glacier/usr/<uid> is exactly what a
|
||||
* squatter would plant. Backstop for the layout invariant above, so a
|
||||
* hand-loosened /glacier/usr can't silently turn into one user writing
|
||||
* another's store. */
|
||||
static inline gl_usr_base_status_t
|
||||
gl_usr_base_check(uid_t uid)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (lstat(GL_USR_ROOT, &st) != 0) { return GL_USR_BASE_MISSING; }
|
||||
if (!S_ISDIR(st.st_mode) || st.st_uid != 0 ||
|
||||
(st.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
|
||||
return GL_USR_BASE_UNSAFE_ROOT;
|
||||
}
|
||||
|
||||
char base[PATH_MAX];
|
||||
gl_usr_base(uid, base, sizeof(base));
|
||||
|
||||
if (lstat(base, &st) != 0) { return GL_USR_BASE_MISSING; }
|
||||
if (!S_ISDIR(st.st_mode) || st.st_uid != uid ||
|
||||
(st.st_mode & (S_IRWXG | S_IRWXO)) != 0) {
|
||||
return GL_USR_BASE_UNSAFE;
|
||||
}
|
||||
|
||||
return GL_USR_BASE_OK;
|
||||
}
|
||||
|
||||
static inline int
|
||||
gl_mkdirp(const char *path, mode_t mode)
|
||||
{
|
||||
@@ -52,7 +110,13 @@ gl_rmdir_recursive(const char *path)
|
||||
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(dir))) {
|
||||
if (ent->d_name[0] == '.') { continue; }
|
||||
/* Only . and .. — skipping every dotfile left any directory
|
||||
* containing one (a package's own dotfiles, the store's
|
||||
* .displaced tree) impossible to rmdir, so the old/ and stage
|
||||
* wipes silently failed and the next commit hit ENOTEMPTY. */
|
||||
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char full[PATH_MAX];
|
||||
snprintf(full, sizeof(full), "%s/%s", path, ent->d_name);
|
||||
|
||||
Reference in new issue
Block a user