From 0681b1db1efde697967fa6685e1c0809858e3094 Mon Sep 17 00:00:00 2001 From: Dapeng Gao Date: Thu, 9 Jan 2025 15:14:39 +0000 Subject: [PATCH] proc: Teach kernel to extract list of compartment names from RTLD --- sys/cheri/c18n.h | 13 ++++ sys/kern/kern_proc.c | 149 ++++++++++++++++++++++++++++++++++++++++++- sys/sys/sysctl.h | 1 + 3 files changed, 160 insertions(+), 3 deletions(-) diff --git a/sys/cheri/c18n.h b/sys/cheri/c18n.h index fe1e4cd0b64c..4d80b5d4ab6a 100644 --- a/sys/cheri/c18n.h +++ b/sys/cheri/c18n.h @@ -85,6 +85,19 @@ struct cheri_c18n_info { void * __kerncap comparts; }; +/* + * The interface provided by the kernel via sysctl for compartmentalization + * monitoring tools such as procstat. + */ +#define CHERI_C18N_COMPART_MAXNAME 56 +#define CHERI_C18N_COMPART_LAST -1 + +struct cheri_c18n_compart { + ssize_t ccc_id; + char ccc_name[CHERI_C18N_COMPART_MAXNAME]; + char _ccc_pad[64]; /* Shrink as new fields added above. */ +}; + #ifndef IN_RTLD #undef _Atomic #endif diff --git a/sys/kern/kern_proc.c b/sys/kern/kern_proc.c index a3ee93da1008..a1d931d33110 100644 --- a/sys/kern/kern_proc.c +++ b/sys/kern/kern_proc.c @@ -2534,8 +2534,8 @@ sysctl_kern_proc_c18n(SYSCTL_HANDLER_ARGS) info.version != CHERI_C18N_INFO_VERSION || info.stats_size == 0 || info.stats_size > RTLD_C18N_STATS_MAX_SIZE || - !__CAP_CHECK(info.stats, info.stats_size) || - (cheri_getperm(info.stats) & CHERI_PERM_LOAD) == 0) { + !cheri_can_access(info.stats, CHERI_PERM_LOAD, + (__cheri_addr ptraddr_t)info.stats, info.stats_size)) { error = ENOEXEC; goto out; } @@ -2544,7 +2544,7 @@ sysctl_kern_proc_c18n(SYSCTL_HANDLER_ARGS) n = proc_readmem(curthread, p, (__cheri_addr vm_offset_t)info.stats, buffer, info.stats_size); if (n != info.stats_size) { - error = ENOMEM; + error = EFAULT; goto out_free; } error = SYSCTL_OUT(req, buffer, info.stats_size); @@ -2554,6 +2554,145 @@ sysctl_kern_proc_c18n(SYSCTL_HANDLER_ARGS) PRELE(p); return (error); } + +/* + * The implementation of proc_read_string() above does not stop at null + * terminators, which we would like to do. Return -1 on failure (e.g., fault) + * and otherwise the number of bytes read and a properly terminated (albeit + * possible truncated) string. + */ +static int +proc_read_string_properly(struct thread *td, struct proc *p, + const char * __capability sptr, char *buf, size_t len) +{ + ssize_t readlen; + size_t n; + + KASSERT(len >= 1, ("%s: Buffer too short", __func__)); + if (len < 1) + return (-1); + for (n = 0; n < len - 1; n++) { + if (!cheri_can_access(sptr, CHERI_PERM_LOAD, + (__cheri_addr ptraddr_t)&sptr[n], 1)) + return (-1); + readlen = proc_readmem(td, p, + (__cheri_addr vm_offset_t)&sptr[n], &buf[n], 1); + if (readlen != 1) + return (-1); + if (buf[n] == '\0') + break; + } + /* Unconditionally enforce termination. */ + buf[n++] = '\0'; + return (n); +} + +/* + * If usefully accessible, return a c18n compartment list from the target + * process. + */ +static int +sysctl_kern_proc_c18n_compartments(SYSCTL_HANDLER_ARGS) +{ + int error, *name = (int *)arg1; + u_int namelen = arg2; + struct proc *p; + struct cheri_c18n_info info; + struct cheri_c18n_compart compart; + char * __capability namep; + char * __capability namepp; + size_t len, i, gen; + + if (namelen != 1) + return (EINVAL); + + error = pget((pid_t)name[0], PGET_WANTREAD, &p); + if (error != 0) + return (error); + + if ((p->p_flag & P_SYSTEM) != 0 || + SV_PROC_FLAG(p, SV_CHERI) == 0 || + p->p_c18n_info == NULL) + goto out; + + len = proc_readmem_cap(curthread, p, (vm_offset_t)p->p_c18n_info, &info, + sizeof(info)); + /* + * If there is a version mismatch or the compartment array is malformed, + * error out. + */ + if (len != sizeof(info) || + info.version != CHERI_C18N_INFO_VERSION || + info.comparts_gen % 2 != 0 || + info.comparts_entry_size < sizeof(namep)) { + error = ENOEXEC; + goto out; + } + + /* + * One by one, copy compartment names out of the target process's + * memory, and into a template struct that we copy out to userspace. + */ + for (i = 0; i < info.comparts_size; ++i) { + /* Initialize userspace structure, including padding. */ + bzero(&compart, sizeof(compart)); + compart.ccc_id = i; + + namepp = (char * __capability)info.comparts + + i * info.comparts_entry_size; + if (!cheri_can_access(namepp, + CHERI_PERM_LOAD | CHERI_PERM_LOAD_CAP, + (__cheri_addr ptraddr_t)namepp, sizeof(namep))) { + error = ENOEXEC; + goto out; + } + + /* Copy in next compartment-name string pointer. */ + len = proc_readmem_cap(curthread, p, + (__cheri_addr vm_offset_t)namepp, &namep, sizeof(namep)); + if (len != sizeof(namep)) { + error = EFAULT; + goto out; + } + + /* + * Copy in compartment name string. Capability access checks are + * performed by proc_read_string_properly(). + */ + len = proc_read_string_properly(curthread, p, namep, + compart.ccc_name, sizeof(compart.ccc_name)); + if (len == -1) { + error = EFAULT; + goto out; + } + + /* If the generation counter has changed, abort. */ + len = proc_readmem_cap(curthread, p, + (vm_offset_t)&p->p_c18n_info->comparts_gen, &gen, + sizeof(gen)); + if (len != sizeof(gen)) { + error = EFAULT; + goto out; + } + if (gen != info.comparts_gen) { + error = ENOEXEC; + goto out; + } + + /* Copy out userspace structure. */ + error = SYSCTL_OUT(req, &compart, sizeof(compart)); + if (error != 0) + goto out; + } + + /* Copy out a last structure with ID terminating list. */ + bzero(&compart, sizeof(compart)); + compart.ccc_id = CHERI_C18N_COMPART_LAST; + error = SYSCTL_OUT(req, &compart, sizeof(compart)); +out: + PRELE(p); + return (error); +} #endif /* @@ -3744,6 +3883,10 @@ static SYSCTL_NODE(_kern_proc, KERN_PROC_AUXV, auxv, CTLFLAG_RD | static SYSCTL_NODE(_kern_proc, KERN_PROC_C18N, c18n, CTLFLAG_RD | CTLFLAG_MPSAFE, sysctl_kern_proc_c18n, "Compartmentalisation statistics"); + +static SYSCTL_NODE(_kern_proc, KERN_PROC_C18N_COMPARTS, c18n_compartments, + CTLFLAG_RD | CTLFLAG_MPSAFE, sysctl_kern_proc_c18n_compartments, + "Compartment list"); #endif static SYSCTL_NODE(_kern_proc, KERN_PROC_PATHNAME, pathname, CTLFLAG_RD | diff --git a/sys/sys/sysctl.h b/sys/sys/sysctl.h index a3199a423313..89d19c2e8348 100644 --- a/sys/sys/sysctl.h +++ b/sys/sys/sysctl.h @@ -1065,6 +1065,7 @@ TAILQ_HEAD(sysctl_ctx_list, sysctl_ctx_entry); #define KERN_PROC_REVOKER_STATE 47 /* revoker state */ #define KERN_PROC_REVOKER_EPOCH 48 /* revoker epoch */ #define KERN_PROC_C18N 49 /* compartmentalisation statistics */ +#define KERN_PROC_C18N_COMPARTS 50 /* compartment list */ /* * KERN_IPC identifiers