Skip to content

Commit

Permalink
pwcache: Fill the user/group caches lazily
Browse files Browse the repository at this point in the history
Iterating all the users/groups can be expensive, especially with NSS.
Android has so many that it doesn't even return them all from
get{pw,gr}ent() for performance reasons, leading to incorrect behaviour
of -user/-group/etc.
  • Loading branch information
tavianator committed Nov 9, 2022
1 parent dd68085 commit b41dca5
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 366 deletions.
50 changes: 15 additions & 35 deletions src/ctx.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@ struct bfs_ctx *bfs_ctx_new(void) {
ctx->cerr = NULL;

ctx->users = NULL;
ctx->users_error = 0;
ctx->groups = NULL;
ctx->groups_error = 0;

ctx->mtab = NULL;
ctx->mtab_error = 0;
Expand All @@ -95,45 +93,27 @@ struct bfs_ctx *bfs_ctx_new(void) {
ctx->nfiles = 0;

struct rlimit rl;
if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
ctx->nofile_soft = rl.rlim_cur;
ctx->nofile_hard = rl.rlim_max;
} else {
ctx->nofile_soft = 1024;
ctx->nofile_hard = RLIM_INFINITY;
if (getrlimit(RLIMIT_NOFILE, &rl) != 0) {
goto fail;
}
ctx->nofile_soft = rl.rlim_cur;
ctx->nofile_hard = rl.rlim_max;

return ctx;
}

const struct bfs_users *bfs_ctx_users(const struct bfs_ctx *ctx) {
struct bfs_ctx *mut = (struct bfs_ctx *)ctx;

if (mut->users_error) {
errno = mut->users_error;
} else if (!mut->users) {
mut->users = bfs_users_parse();
if (!mut->users) {
mut->users_error = errno;
}
ctx->users = bfs_users_new();
if (!ctx->users) {
goto fail;
}

return mut->users;
}

const struct bfs_groups *bfs_ctx_groups(const struct bfs_ctx *ctx) {
struct bfs_ctx *mut = (struct bfs_ctx *)ctx;

if (mut->groups_error) {
errno = mut->groups_error;
} else if (!mut->groups) {
mut->groups = bfs_groups_parse();
if (!mut->groups) {
mut->groups_error = errno;
}
ctx->groups = bfs_groups_new();
if (!ctx->groups) {
goto fail;
}

return mut->groups;
return ctx;

fail:
bfs_ctx_free(ctx);
return NULL;
}

const struct bfs_mtab *bfs_ctx_mtab(const struct bfs_ctx *ctx) {
Expand Down
24 changes: 1 addition & 23 deletions src/ctx.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,8 @@ struct bfs_ctx {
/** Colored stderr. */
struct CFILE *cerr;

/** User table. */
/** User cache. */
struct bfs_users *users;
/** The error that occurred parsing the user table, if any. */
int users_error;
/** Group table. */
struct bfs_groups *groups;
/** The error that occurred parsing the group table, if any. */
Expand Down Expand Up @@ -137,26 +135,6 @@ struct bfs_ctx {
*/
struct bfs_ctx *bfs_ctx_new(void);

/**
* Get the users table.
*
* @param ctx
* The bfs context.
* @return
* The cached users table, or NULL on failure.
*/
const struct bfs_users *bfs_ctx_users(const struct bfs_ctx *ctx);

/**
* Get the groups table.
*
* @param ctx
* The bfs context.
* @return
* The cached groups table, or NULL on failure.
*/
const struct bfs_groups *bfs_ctx_groups(const struct bfs_ctx *ctx);

/**
* Get the mount table.
*
Expand Down
25 changes: 10 additions & 15 deletions src/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -309,13 +309,11 @@ bool eval_nogroup(const struct bfs_expr *expr, struct bfs_eval *state) {
return false;
}

const struct bfs_groups *groups = bfs_ctx_groups(state->ctx);
if (!groups) {
const struct group *grp = bfs_getgrgid(state->ctx->groups, statbuf->gid);
if (errno != 0) {
eval_report_error(state);
return false;
}

return bfs_getgrgid(groups, statbuf->gid) == NULL;
return grp == NULL;
}

/**
Expand All @@ -327,13 +325,11 @@ bool eval_nouser(const struct bfs_expr *expr, struct bfs_eval *state) {
return false;
}

const struct bfs_users *users = bfs_ctx_users(state->ctx);
if (!users) {
const struct passwd *pwd = bfs_getpwuid(state->ctx->users, statbuf->uid);
if (errno != 0) {
eval_report_error(state);
return false;
}

return bfs_getpwuid(users, statbuf->uid) == NULL;
return pwd == NULL;
}

/**
Expand Down Expand Up @@ -642,16 +638,15 @@ bool eval_perm(const struct bfs_expr *expr, struct bfs_eval *state) {
bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) {
CFILE *cfile = expr->cfile;
FILE *file = cfile->file;
const struct bfs_users *users = bfs_ctx_users(state->ctx);
const struct bfs_groups *groups = bfs_ctx_groups(state->ctx);
const struct bfs_ctx *ctx = state->ctx;
const struct BFTW *ftwbuf = state->ftwbuf;
const struct bfs_stat *statbuf = eval_stat(state);
if (!statbuf) {
goto done;
}

uintmax_t ino = statbuf->ino;
uintmax_t block_size = state->ctx->posixly_correct ? 512 : 1024;
uintmax_t block_size = ctx->posixly_correct ? 512 : 1024;
uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + block_size - 1)/block_size;
char mode[11];
xstrmode(statbuf->mode, mode);
Expand All @@ -662,7 +657,7 @@ bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) {
}

uintmax_t uid = statbuf->uid;
const struct passwd *pwd = users ? bfs_getpwuid(users, uid) : NULL;
const struct passwd *pwd = bfs_getpwuid(ctx->users, uid);
if (pwd) {
if (fprintf(file, " %-8s", pwd->pw_name) < 0) {
goto error;
Expand All @@ -674,7 +669,7 @@ bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) {
}

uintmax_t gid = statbuf->gid;
const struct group *grp = groups ? bfs_getgrgid(groups, gid) : NULL;
const struct group *grp = bfs_getgrgid(ctx->groups, gid);
if (grp) {
if (fprintf(file, " %-8s", grp->gr_name) < 0) {
goto error;
Expand Down
19 changes: 11 additions & 8 deletions src/opt.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include "expr.h"
#include "pwcache.h"
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
Expand Down Expand Up @@ -789,25 +790,27 @@ static void infer_icmp_facts(struct opt_state *state, const struct bfs_expr *exp
static void infer_gid_facts(struct opt_state *state, const struct bfs_expr *expr) {
infer_icmp_facts(state, expr, GID_RANGE);

const struct bfs_groups *groups = bfs_ctx_groups(state->ctx);
struct range *range = &state->facts_when_true.ranges[GID_RANGE];
if (groups && range->min == range->max) {
if (range->min == range->max) {
gid_t gid = range->min;
bool nogroup = !bfs_getgrgid(groups, gid);
constrain_pred(&state->facts_when_true.preds[NOGROUP_PRED], nogroup);
bool nogroup = !bfs_getgrgid(state->ctx->groups, gid);
if (errno == 0) {
constrain_pred(&state->facts_when_true.preds[NOGROUP_PRED], nogroup);
}
}
}

/** Infer data flow facts about a -uid expression. */
static void infer_uid_facts(struct opt_state *state, const struct bfs_expr *expr) {
infer_icmp_facts(state, expr, UID_RANGE);

const struct bfs_users *users = bfs_ctx_users(state->ctx);
struct range *range = &state->facts_when_true.ranges[UID_RANGE];
if (users && range->min == range->max) {
if (range->min == range->max) {
uid_t uid = range->min;
bool nouser = !bfs_getpwuid(users, uid);
constrain_pred(&state->facts_when_true.preds[NOUSER_PRED], nouser);
bool nouser = !bfs_getpwuid(state->ctx->users, uid);
if (errno == 0) {
constrain_pred(&state->facts_when_true.preds[NOUSER_PRED], nouser);
}
}
}

Expand Down
68 changes: 26 additions & 42 deletions src/parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -1526,12 +1526,6 @@ static struct bfs_expr *parse_fls(struct parser_state *state, int arg1, int arg2
expr_set_always_true(expr);
expr->cost = PRINT_COST;
expr->reftime = state->now;

// We'll need these for user/group names, so initialize them now to
// avoid EMFILE later
bfs_ctx_users(state->ctx);
bfs_ctx_groups(state->ctx);

return expr;

fail:
Expand Down Expand Up @@ -1647,27 +1641,23 @@ static struct bfs_expr *parse_group(struct parser_state *state, int arg1, int ar
return NULL;
}

const struct bfs_groups *groups = bfs_ctx_groups(state->ctx);
if (!groups) {
parse_expr_error(state, expr, "Couldn't parse the group table: %m.\n");
goto fail;
}

const struct group *grp = bfs_getgrnam(groups, expr->argv[1]);
const struct group *grp = bfs_getgrnam(state->ctx->groups, expr->argv[1]);
if (grp) {
expr->num = grp->gr_gid;
expr->int_cmp = BFS_INT_EQUAL;
} else if (looks_like_icmp(expr->argv[1])) {
if (!parse_icmp(state, expr, 0)) {
goto fail;
}
} else if (errno) {
parse_expr_error(state, expr, "%m.\n");
goto fail;
} else {
parse_expr_error(state, expr, "No such group.\n");
goto fail;
}

expr->cost = STAT_COST;

return expr;

fail:
Expand Down Expand Up @@ -1703,27 +1693,23 @@ static struct bfs_expr *parse_user(struct parser_state *state, int arg1, int arg
return NULL;
}

const struct bfs_users *users = bfs_ctx_users(state->ctx);
if (!users) {
parse_expr_error(state, expr, "Couldn't parse the user table: %m.\n");
goto fail;
}

const struct passwd *pwd = bfs_getpwnam(users, expr->argv[1]);
const struct passwd *pwd = bfs_getpwnam(state->ctx->users, expr->argv[1]);
if (pwd) {
expr->num = pwd->pw_uid;
expr->int_cmp = BFS_INT_EQUAL;
} else if (looks_like_icmp(expr->argv[1])) {
if (!parse_icmp(state, expr, 0)) {
goto fail;
}
} else if (errno) {
parse_expr_error(state, expr, "%m.\n");
goto fail;
} else {
parse_expr_error(state, expr, "No such user.\n");
goto fail;
}

expr->cost = STAT_COST;

return expr;

fail:
Expand Down Expand Up @@ -1785,12 +1771,6 @@ static struct bfs_expr *parse_ls(struct parser_state *state, int arg1, int arg2)

init_print_expr(state, expr);
expr->reftime = state->now;

// We'll need these for user/group names, so initialize them now to
// avoid EMFILE later
bfs_ctx_users(state->ctx);
bfs_ctx_groups(state->ctx);

return expr;
}

Expand Down Expand Up @@ -2005,16 +1985,18 @@ static struct bfs_expr *parse_newerxy(struct parser_state *state, int arg1, int
* Parse -nogroup.
*/
static struct bfs_expr *parse_nogroup(struct parser_state *state, int arg1, int arg2) {
if (!bfs_ctx_groups(state->ctx)) {
parse_error(state, "Couldn't parse the group table: %m.\n");
struct bfs_expr *expr = parse_nullary_test(state, eval_nogroup);
if (!expr) {
return NULL;
}

struct bfs_expr *expr = parse_nullary_test(state, eval_nogroup);
if (expr) {
expr->cost = STAT_COST;
expr->probability = 0.01;
}
expr->cost = STAT_COST;
expr->probability = 0.01;

// Who knows how many FDs getgrgid_r() needs? Probably at least one for
// /etc/group
expr->ephemeral_fds = 1;

return expr;
}

Expand Down Expand Up @@ -2051,16 +2033,18 @@ static struct bfs_expr *parse_noleaf(struct parser_state *state, int arg1, int a
* Parse -nouser.
*/
static struct bfs_expr *parse_nouser(struct parser_state *state, int arg1, int arg2) {
if (!bfs_ctx_users(state->ctx)) {
parse_error(state, "Couldn't parse the user table: %m.\n");
struct bfs_expr *expr = parse_nullary_test(state, eval_nouser);
if (!expr) {
return NULL;
}

struct bfs_expr *expr = parse_nullary_test(state, eval_nouser);
if (expr) {
expr->cost = STAT_COST;
expr->probability = 0.01;
}
expr->cost = STAT_COST;
expr->probability = 0.01;

// Who knows how many FDs getpwuid_r() needs? Probably at least one for
// /etc/passwd
expr->ephemeral_fds = 1;

return expr;
}

Expand Down
Loading

0 comments on commit b41dca5

Please sign in to comment.