minix/servers/procfs/tree.c
David van Moolenbroek 0812293b47 procfs: fix for PID reuse between updates
In certain cases, a process ID may be reused between two lazy updates
of procfs's PID table. If the new associated process slot has a lower
index than the old one, this will trigger an assert in vtreefs, as the
new PID name entry is added before the old one is removed. This patch
fixes the problem by always first removing old PID name entries before
adding new ones.

Bug reported by Stephen Hatton.
2011-11-01 23:50:55 +00:00

478 lines
14 KiB
C

/* ProcFS - tree.c - by Alen Stojanov and David van Moolenbroek */
#include "inc.h"
PUBLIC struct proc proc[NR_PROCS + NR_TASKS];
PUBLIC struct mproc mproc[NR_PROCS];
PUBLIC struct fproc fproc[NR_PROCS];
PRIVATE int nr_pid_entries;
/*===========================================================================*
* slot_in_use *
*===========================================================================*/
PRIVATE int slot_in_use(int slot)
{
/* Return whether the given slot is in use by a process.
*/
return (proc[slot].p_rts_flags != RTS_SLOT_FREE ||
(slot >= NR_TASKS &&
(mproc[slot - NR_TASKS].mp_flags & IN_USE)));
}
/*===========================================================================*
* check_owner *
*===========================================================================*/
PRIVATE int check_owner(struct inode *node, int slot)
{
/* Check if the owner user and group ID of the inode are still in sync
* the current effective user and group ID of the given process.
*/
struct inode_stat stat;
if (slot < NR_TASKS) return TRUE;
get_inode_stat(node, &stat);
return (stat.uid == mproc[slot - NR_TASKS].mp_effuid &&
stat.gid == mproc[slot - NR_TASKS].mp_effgid);
}
/*===========================================================================*
* make_stat *
*===========================================================================*/
PRIVATE void make_stat(struct inode_stat *stat, int slot, int index)
{
/* Fill in an inode_stat structure for the given process slot and
* per-pid file index (or NO_INDEX for the process subdirectory root).
*/
if (index == NO_INDEX)
stat->mode = DIR_ALL_MODE;
else
stat->mode = pid_files[index].mode;
if (slot < NR_TASKS) {
stat->uid = SUPER_USER;
stat->gid = SUPER_USER;
} else {
stat->uid = mproc[slot - NR_TASKS].mp_effuid;
stat->gid = mproc[slot - NR_TASKS].mp_effgid;
}
stat->size = 0;
stat->dev = NO_DEV;
}
/*===========================================================================*
* dir_is_pid *
*===========================================================================*/
PRIVATE int dir_is_pid(struct inode *node)
{
/* Return whether the given node is a PID directory.
*/
return (get_parent_inode(node) == get_root_inode() &&
get_inode_index(node) != NO_INDEX);
}
/*===========================================================================*
* update_tables *
*===========================================================================*/
PRIVATE int update_tables(void)
{
/* Get the process tables from the kernel, PM, and VFS.
* Check the magic number in the kernel table entries.
*/
int r, slot;
if ((r = sys_getproctab(proc)) != OK) return r;
for (slot = 0; slot < NR_PROCS + NR_TASKS; slot++) {
if (proc[slot].p_magic != PMAGIC) {
printf("PROCFS: system version mismatch!\n");
return EINVAL;
}
}
if ((r = getsysinfo(PM_PROC_NR, SI_PROC_TAB, mproc)) != OK) return r;
if ((r = getsysinfo(VFS_PROC_NR, SI_PROC_TAB, fproc)) != OK) return r;
return OK;
}
/*===========================================================================*
* init_tree *
*===========================================================================*/
PUBLIC int init_tree(void)
{
/* Initialize this module, before VTreeFS is started. As part of the
* process, check if we're not compiled against a kernel different from
* the one that is running at the moment.
*/
int i, r;
if ((r = update_tables()) != OK)
return r;
/* Get the maximum number of entries that we may add to each PID's
* directory. We could just default to a large value, but why not get
* it right?
*/
for (i = 0; pid_files[i].name != NULL; i++);
nr_pid_entries = i;
return OK;
}
/*===========================================================================*
* out_of_inodes *
*===========================================================================*/
PRIVATE void out_of_inodes(void)
{
/* Out of inodes - the NR_INODES value is set too low. We can not do
* much, but we might be able to continue with degraded functionality,
* so do not panic. If the NR_INODES value is not below the *crucial*
* minimum, the symptom of this case will be an incomplete listing of
* the main proc directory.
*/
static int warned = FALSE;
if (warned == FALSE) {
printf("PROCFS: out of inodes!\n");
warned = TRUE;
}
}
/*===========================================================================*
* construct_pid_dirs *
*===========================================================================*/
PRIVATE void construct_pid_dirs(void)
{
/* Regenerate the set of PID directories in the root directory of the
* file system. Add new directories and delete old directories as
* appropriate; leave unchanged those that should remain the same.
*
* We have to make two passes. Otherwise, we would trigger a vtreefs
* assert when we add an entry for a PID before deleting the previous
* entry for that PID. While rare, such rapid PID reuse does occur in
* practice.
*/
struct inode *root, *node;
struct inode_stat stat;
char name[PNAME_MAX+1];
pid_t pid;
int i;
root = get_root_inode();
/* First pass: delete old entries. */
for (i = 0; i < NR_PROCS + NR_TASKS; i++) {
/* Do we already have an inode associated with this slot? */
node = get_inode_by_index(root, i);
if (node == NULL)
continue;
/* If the process slot is not in use, delete the associated
* inode.
*/
if (!slot_in_use(i)) {
delete_inode(node);
continue;
}
/* Otherwise, get the process ID. */
if (i < NR_TASKS)
pid = (pid_t) (i - NR_TASKS);
else
pid = mproc[i - NR_TASKS].mp_pid;
/* If there is an old entry, see if the pid matches the current
* entry, and the owner is still the same. Otherwise, delete
* the old entry first. We reconstruct the entire subtree even
* if only the owner changed, for security reasons: if a
* process could keep open a file or directory across the owner
* change, it might be able to access information it shouldn't.
*/
if (pid != (pid_t) get_inode_cbdata(node) ||
!check_owner(node, i))
delete_inode(node);
}
/* Second pass: add new entries. */
for (i = 0; i < NR_PROCS + NR_TASKS; i++) {
/* If the process slot is not in use, skip this slot. */
if (!slot_in_use(i))
continue;
/* If we have an inode associated with this slot, we have
* already checked it to be up-to-date above.
*/
if (get_inode_by_index(root, i) != NULL)
continue;
/* Get the process ID. */
if (i < NR_TASKS)
pid = (pid_t) (i - NR_TASKS);
else
pid = mproc[i - NR_TASKS].mp_pid;
/* Add the entry for the process slot. */
sprintf(name, "%d", pid);
make_stat(&stat, i, NO_INDEX);
node = add_inode(root, name, i, &stat, nr_pid_entries,
(cbdata_t) pid);
if (node == NULL)
out_of_inodes();
}
}
/*===========================================================================*
* make_one_pid_entry *
*===========================================================================*/
PRIVATE void make_one_pid_entry(struct inode *parent, char *name, int slot)
{
/* Construct one file in a PID directory, if a file with the given name
* should exist at all.
*/
struct inode *node;
struct inode_stat stat;
int i;
/* Don't readd if it is already there. */
node = get_inode_by_name(parent, name);
if (node != NULL)
return;
/* Only add the file if it is a known, registered name. */
for (i = 0; pid_files[i].name != NULL; i++) {
if (!strcmp(name, pid_files[i].name)) {
make_stat(&stat, slot, i);
node = add_inode(parent, name, i, &stat,
(index_t) 0, (cbdata_t) 0);
if (node == NULL)
out_of_inodes();
break;
}
}
}
/*===========================================================================*
* make_all_pid_entries *
*===========================================================================*/
PRIVATE void make_all_pid_entries(struct inode *parent, int slot)
{
/* Construct all files in a PID directory.
*/
struct inode *node;
struct inode_stat stat;
int i;
for (i = 0; pid_files[i].name != NULL; i++) {
node = get_inode_by_index(parent, i);
if (node != NULL)
continue;
make_stat(&stat, slot, i);
node = add_inode(parent, pid_files[i].name, i, &stat,
(index_t) 0, (cbdata_t) 0);
if (node == NULL)
out_of_inodes();
}
}
/*===========================================================================*
* construct_pid_entries *
*===========================================================================*/
PRIVATE void construct_pid_entries(struct inode *parent, char *name)
{
/* Construct one requested file entry, or all file entries, in a PID
* directory.
*/
int slot;
slot = get_inode_index(parent);
assert(slot >= 0 && slot < NR_TASKS + NR_PROCS);
/* If this process is already gone, delete the directory now. */
if (!slot_in_use(slot)) {
delete_inode(parent);
return;
}
/* If a specific file name is being looked up, see if we have to add
* an inode for that file. If the directory contents are being
* retrieved, add all files that have not yet been added.
*/
if (name != NULL)
make_one_pid_entry(parent, name, slot);
else
make_all_pid_entries(parent, slot);
}
/*===========================================================================*
* pid_read *
*===========================================================================*/
PRIVATE void pid_read(struct inode *node)
{
/* Data is requested from one of the files in a PID directory. Call the
* function that is responsible for generating the data for that file.
*/
struct inode *parent;
int slot, index;
/* Get the slot number of the process. Note that this currently will
* not work for files not in the top-level pid subdirectory.
*/
parent = get_parent_inode(node);
slot = get_inode_index(parent);
/* Get this file's index number. */
index = get_inode_index(node);
/* Call the handler procedure for the file. */
((_PROTOTYPE(void (*), (int))) pid_files[index].data)(slot);
}
/*===========================================================================*
* pid_link *
*===========================================================================*/
PRIVATE int pid_link(struct inode *node, char *ptr, int max)
{
/* The contents of a symbolic link in a PID directory are requested.
* This function is a placeholder for future use.
*/
/* Nothing yet. */
strcpy(ptr, "");
return OK;
}
/*===========================================================================*
* lookup_hook *
*===========================================================================*/
PUBLIC int lookup_hook(struct inode *parent, char *name, cbdata_t cbdata)
{
/* Path name resolution hook, for a specific parent and name pair.
* If needed, update our own view of the system first; after that,
* determine whether we need to (re)generate certain files.
*/
static clock_t last_update = 0;
clock_t now;
int r;
/* Update lazily for lookups, as this gets too expensive otherwise.
* Alternative: pull in only PM's table?
*/
if ((r = getuptime(&now)) != OK)
panic(__FILE__, "unable to get uptime", r);
if (last_update != now) {
update_tables();
last_update = now;
}
/* If the parent is the root directory, we must now reconstruct all
* entries, because some of them might have been garbage collected.
* We must update the entire tree at once; if we update individual
* entries, we risk name collisions.
*/
if (parent == get_root_inode()) {
construct_pid_dirs();
}
/* If the parent is a process directory, we may need to (re)construct
* the entry being looked up.
*/
else if (dir_is_pid(parent)) {
/* We might now have deleted our current containing directory;
* construct_pid_entries() will take care of this case.
*/
construct_pid_entries(parent, name);
}
return OK;
}
/*===========================================================================*
* getdents_hook *
*===========================================================================*/
PUBLIC int getdents_hook(struct inode *node, cbdata_t cbdata)
{
/* Directory entry retrieval hook, for potentially all files in a
* directory. Make sure that all files that are supposed to be
* returned, are actually part of the virtual tree.
*/
if (node == get_root_inode()) {
update_tables();
construct_pid_dirs();
} else if (dir_is_pid(node)) {
construct_pid_entries(node, NULL /*name*/);
}
return OK;
}
/*===========================================================================*
* read_hook *
*===========================================================================*/
PUBLIC int read_hook(struct inode *node, off_t off, char **ptr,
size_t *len, cbdata_t cbdata)
{
/* Regular file read hook. Call the appropriate callback function to
* generate and return the data.
*/
buf_init(off, *len);
/* Populate the buffer with the proper content. */
if (get_inode_index(node) != NO_INDEX) {
pid_read(node);
} else {
((_PROTOTYPE(void (*), (void))) cbdata)();
}
*len = buf_get(ptr);
return OK;
}
/*===========================================================================*
* rdlink_hook *
*===========================================================================*/
PUBLIC int rdlink_hook(struct inode *node, char *ptr, size_t max,
cbdata_t cbdata)
{
/* Symbolic link resolution hook. Not used yet.
*/
struct inode *parent;
/* Get the parent inode. */
parent = get_parent_inode(node);
/* If the parent inode is a pid directory, call the pid handler.
*/
if (parent != NULL && dir_is_pid(parent))
pid_link(node, ptr, max);
return OK;
}