summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2025-04-17 14:14:23 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2025-04-19 13:05:48 -0600
commit3862c9baf9feb51bddc98ec4f3d305404bba1f50 (patch)
tree81af96afb326e4ed27419cff3e611bf92284e3a1
parenta89ed185cf57cbfa92b3a63705db3f159004758c (diff)
lib9p_srv: Proper permission checks, with stubbed group functionalitylukeshu/9p-perms
-rw-r--r--lib9p/srv.c140
1 files changed, 129 insertions, 11 deletions
diff --git a/lib9p/srv.c b/lib9p/srv.c
index fb2bd07..2feed56 100644
--- a/lib9p/srv.c
+++ b/lib9p/srv.c
@@ -165,17 +165,6 @@ static inline enum srv_filetype srv_qid_filetype(struct lib9p_qid qid) {
return SRV_FILETYPE_FILE;
}
-static inline bool srv_check_perm(struct srv_req *ctx, struct lib9p_srv_stat *stat, uint8_t action) {
- assert(ctx);
- assert(stat);
- assert(action);
-
- /* TODO actually check user and group instead of just assuming "other". */
- uint8_t mode = (uint8_t)(stat->mode & 07);
-
- return mode & action;
-}
-
[[gnu::unused]]
static struct lib9p_srv_userid *srv_userid_new(struct lib9p_s name
#if CONFIG_9P_ENABLE_9P2000_u || CONFIG_9P_ENABLE_9P2000_L
@@ -230,6 +219,46 @@ static struct lib9p_srv_userid *srv_userid_incref(struct lib9p_srv_userid *useri
return userid;
}
+static inline bool srv_user_is_same(struct lib9p_srv_userid *a, struct lib9p_srv_userid *b) {
+ assert(a);
+ assert(b);
+ return lib9p_str_eq(a->name, b->name)
+#if CONFIG_9P_ENABLE_9P2000_u || CONFIG_9P_ENABLE_9P2000_L
+ && (a->num == b->num)
+#endif
+ ;
+}
+
+static inline bool srv_user_is_member(struct lib9p_srv_userid *user, struct lib9p_srv_userid *group) {
+ assert(user);
+ assert(group);
+ /* TODO: Add a way to check group membership. */
+ return false;
+}
+
+static inline bool srv_user_is_leader(struct lib9p_srv_userid *user, struct lib9p_srv_userid *group) {
+ assert(user);
+ assert(group);
+ /* TODO: Add a way to check group leadership. */
+ return false;
+}
+
+static inline bool srv_check_perm(struct srv_req *ctx, struct lib9p_srv_stat *stat, uint8_t requested) {
+ assert(ctx);
+ assert(stat);
+ assert(requested);
+
+ uint8_t allowed;
+ if (srv_user_is_same(ctx->user, &stat->owner_uid))
+ allowed = (stat->mode >> 6) & 07;
+ else if (srv_user_is_member(ctx->user, &stat->owner_gid))
+ allowed = (stat->mode >> 3) & 07;
+ else
+ allowed = (stat->mode >> 0) & 07;
+
+ return (requested & ~allowed) == 0;
+}
+
/**
* Ensures that `file` is saved into the pathmap, and increments the
* gc_refcount by 1 (for presumptive insertion into the fidmap).
@@ -1472,10 +1501,99 @@ static void handle_Twstat(struct srv_req *ctx,
struct lib9p_msg_Twstat *req) {
srv_handler_common(ctx, wstat, req);
+ struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid);
+ if (!fidinfo) {
+ lib9p_errorf(&ctx->basectx,
+ LIB9P_ERRNO_L_EBADF, "bad file number %"PRIu32, req->fid);
+ return;
+ }
+ struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path);
+ assert(pathinfo);
+
+ ctx->user = srv_userid_incref(fidinfo->user);
+ struct lib9p_srv_stat cur_stat = LO_CALL(pathinfo->file, stat, ctx);
+ if (lib9p_ctx_has_error(&ctx->basectx))
+ return;
+ lib9p_srv_stat_assert(cur_stat);
+
+#define is_str_donttouch(VAL) ((VAL).len == 0)
+#define is_int_donttouch(VAL) ((VAL) == ~(typeof(VAL))0)
+
+ if (!is_str_donttouch(req->stat.name) && !lib9p_str_eq(req->stat.name, cur_stat.name)) {
+ if (pathinfo->parent_dir == fidinfo->path) {
+ lib9p_error(&ctx->basectx,
+ LIB9P_ERRNO_L_EBUSY, "change name: cannot rename root");
+ goto twstat_return;
+ }
+ struct srv_pathinfo *parent = map_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
+ assert(parent);
+ struct lib9p_srv_stat parent_stat = LO_CALL(parent->file, stat, ctx);
+ if (lib9p_ctx_has_error(&ctx->basectx))
+ goto twstat_return;
+ if (!srv_check_perm(ctx, &parent_stat, 0b010)) {
+ lib9p_error(&ctx->basectx,
+ LIB9P_ERRNO_L_EACCES, "change name: you do not have write permission on the parent directory");
+ goto twstat_return;
+ }
+ }
+ if (!is_int_donttouch(req->stat.length) && req->stat.length != cur_stat.size) {
+ if (!srv_check_perm(ctx, &cur_stat, 0b010)) {
+ lib9p_error(&ctx->basectx,
+ LIB9P_ERRNO_L_EACCES, "change length: you do not have write permission");
+ goto twstat_return;
+ }
+ }
+ if (!is_int_donttouch(req->stat.mode) && req->stat.mode != cur_stat.mode) {
+ if (!(srv_user_is_same(ctx->user, &cur_stat.owner_uid) || srv_user_is_leader(ctx->user, &cur_stat.owner_gid))) {
+ lib9p_error(&ctx->basectx,
+ LIB9P_ERRNO_L_EACCES, "change mode: you are neither the owner nor group-leader");
+ goto twstat_return;
+ }
+ }
+ if (!is_int_donttouch(req->stat.mtime) && req->stat.mode != cur_stat.mtime_sec) {
+ if (!(srv_user_is_same(ctx->user, &cur_stat.owner_uid) || srv_user_is_leader(ctx->user, &cur_stat.owner_gid))) {
+ lib9p_error(&ctx->basectx,
+ LIB9P_ERRNO_L_EACCES, "change mtime: you are neither the owner nor group-leader");
+ goto twstat_return;
+ }
+ }
+ if ((!is_str_donttouch(req->stat.owner_gname) && !lib9p_str_eq(req->stat.owner_gname, cur_stat.owner_gid.name))
+#if CONFIG_9P_ENABLE_9P2000_u
+ || (ctx->basectx.version == LIB9P_VER_9P2000_u &&
+ !is_int_donttouch(req->stat.owner_gnum) && req->stat.owner_gnum != cur_stat.owner_gid.num)
+#endif
+ ) {
+#if CONFIG_9P_ENABLE_9P2000_u
+ if (ctx->basectx.version == LIB9P_VER_9P2000_u) {
+ /* TODO: check that gid==n_gid */
+ }
+#endif
+ struct lib9p_srv_userid new_group = srv_userid_new_stack(req->stat.owner_gname, req->stat.owner_gnum);
+ if (srv_user_is_same(ctx->user, &cur_stat.owner_uid)) {
+ if (!srv_user_is_member(ctx->user, &new_group)) {
+ lib9p_error(&ctx->basectx,
+ LIB9P_ERRNO_L_EACCES, "change group: you are not a member of the new group");
+ goto twstat_return;
+ }
+ } else if (srv_user_is_leader(ctx->user, &cur_stat.owner_gid)) {
+ if (!srv_user_is_leader(ctx->user, &new_group)) {
+ lib9p_error(&ctx->basectx,
+ LIB9P_ERRNO_L_EACCES, "change group: you are not a leader of the new group");
+ goto twstat_return;
+ }
+ } else {
+ lib9p_error(&ctx->basectx,
+ LIB9P_ERRNO_L_EACCES, "change group: you are neither the owner nor group-leader");
+ goto twstat_return;
+ }
+ }
+
lib9p_error(&ctx->basectx,
LIB9P_ERRNO_L_EOPNOTSUPP, "wstat not (yet?) implemented");
+ twstat_return:
srv_respond(ctx, wstat, &resp);
+ ctx->user = srv_userid_decref(ctx->user);
}
#endif