Skip to content

Commit

Permalink
Merge pull request #12544 from xokdvium/debugger-use-after-free
Browse files Browse the repository at this point in the history
libexpr: Fix use-after-free of StaticEnv::up
  • Loading branch information
edolstra authored Feb 27, 2025
2 parents b628adc + 0d50045 commit 1293388
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 14 deletions.
2 changes: 1 addition & 1 deletion src/libcmd/repl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ NixRepl::NixRepl(const LookupPath & lookupPath, nix::ref<Store> store, ref<EvalS
: AbstractNixRepl(state)
, debugTraceIndex(0)
, getValues(getValues)
, staticEnv(new StaticEnv(nullptr, state->staticBaseEnv.get()))
, staticEnv(new StaticEnv(nullptr, state->staticBaseEnv))
, runNixPtr{runNix}
, interacter(make_unique<ReadlineLikeInteracter>(getDataDir() + "/repl-history"))
{
Expand Down
18 changes: 9 additions & 9 deletions src/libexpr/nixexpr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
const StaticEnv * curEnv;
Level level;
int withLevel = -1;
for (curEnv = env.get(), level = 0; curEnv; curEnv = curEnv->up, level++) {
for (curEnv = env.get(), level = 0; curEnv; curEnv = curEnv->up.get(), level++) {
if (curEnv->isWith) {
if (withLevel == -1) withLevel = level;
} else {
Expand All @@ -331,7 +331,7 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
"undefined variable '%1%'",
es.symbols[name]
).atPos(pos).debugThrow();
for (auto * e = env.get(); e && !fromWith; e = e->up)
for (auto * e = env.get(); e && !fromWith; e = e->up.get())
fromWith = e->isWith;
this->level = withLevel;
}
Expand Down Expand Up @@ -379,7 +379,7 @@ std::shared_ptr<const StaticEnv> ExprAttrs::bindInheritSources(
// and displacement, and nothing else is allowed to access it. ideally we'd
// not even *have* an expr that grabs anything from this env since it's fully
// invisible, but the evaluator does not allow for this yet.
auto inner = std::make_shared<StaticEnv>(nullptr, env.get(), 0);
auto inner = std::make_shared<StaticEnv>(nullptr, env, 0);
for (auto from : *inheritFromExprs)
from->bindVars(es, env);

Expand All @@ -393,7 +393,7 @@ void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>

if (recursive) {
auto newEnv = [&] () -> std::shared_ptr<const StaticEnv> {
auto newEnv = std::make_shared<StaticEnv>(nullptr, env.get(), attrs.size());
auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs.size());

Displacement displ = 0;
for (auto & i : attrs)
Expand Down Expand Up @@ -440,7 +440,7 @@ void ExprLambda::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
es.exprEnvs.insert(std::make_pair(this, env));

auto newEnv = std::make_shared<StaticEnv>(
nullptr, env.get(),
nullptr, env,
(hasFormals() ? formals->formals.size() : 0) +
(!arg ? 0 : 1));

Expand Down Expand Up @@ -474,7 +474,7 @@ void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
auto newEnv = [&] () -> std::shared_ptr<const StaticEnv> {
auto newEnv = std::make_shared<StaticEnv>(nullptr, env.get(), attrs->attrs.size());
auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs->attrs.size());

Displacement displ = 0;
for (auto & i : attrs->attrs)
Expand All @@ -500,7 +500,7 @@ void ExprWith::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
es.exprEnvs.insert(std::make_pair(this, env));

parentWith = nullptr;
for (auto * e = env.get(); e && !parentWith; e = e->up)
for (auto * e = env.get(); e && !parentWith; e = e->up.get())
parentWith = e->isWith;

/* Does this `with' have an enclosing `with'? If so, record its
Expand All @@ -509,14 +509,14 @@ void ExprWith::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
const StaticEnv * curEnv;
Level level;
prevWith = 0;
for (curEnv = env.get(), level = 1; curEnv; curEnv = curEnv->up, level++)
for (curEnv = env.get(), level = 1; curEnv; curEnv = curEnv->up.get(), level++)
if (curEnv->isWith) {
prevWith = level;
break;
}

attrs->bindVars(es, env);
auto newEnv = std::make_shared<StaticEnv>(this, env.get());
auto newEnv = std::make_shared<StaticEnv>(this, env);
body->bindVars(es, newEnv);
}

Expand Down
7 changes: 5 additions & 2 deletions src/libexpr/nixexpr.hh
Original file line number Diff line number Diff line change
Expand Up @@ -480,13 +480,16 @@ extern ExprBlackHole eBlackHole;
struct StaticEnv
{
ExprWith * isWith;
const StaticEnv * up;
std::shared_ptr<const StaticEnv> up;

// Note: these must be in sorted order.
typedef std::vector<std::pair<Symbol, Displacement>> Vars;
Vars vars;

StaticEnv(ExprWith * isWith, const StaticEnv * up, size_t expectedSize = 0) : isWith(isWith), up(up) {
StaticEnv(ExprWith * isWith, std::shared_ptr<const StaticEnv> up, size_t expectedSize = 0)
: isWith(isWith)
, up(std::move(up))
{
vars.reserve(expectedSize);
};

Expand Down
2 changes: 1 addition & 1 deletion src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ static void scopedImport(EvalState & state, const PosIdx pos, SourcePath & path,
Env * env = &state.allocEnv(vScope->attrs()->size());
env->up = &state.baseEnv;

auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv.get(), vScope->attrs()->size());
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv, vScope->attrs()->size());

unsigned int displ = 0;
for (auto & attr : *vScope->attrs()) {
Expand Down
28 changes: 28 additions & 0 deletions tests/functional/flakes/debugger.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash

source ./common.sh

requireGit

flakeDir="$TEST_ROOT/flake"
createGitRepo "$flakeDir"

cat >"$flakeDir/flake.nix" <<EOF
{
inputs = {
};
outputs =
_:
let
in
{
packages.$system.default = throw "oh no";
};
}
EOF

git -C "$flakeDir" add flake.nix

# regression #12527 and #11286
echo ":env" | expect 1 nix eval "$flakeDir#packages.${system}.default" --debugger
3 changes: 2 additions & 1 deletion tests/functional/flakes/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ suites += {
'commit-lock-file-summary.sh',
'non-flake-inputs.sh',
'relative-paths.sh',
'symlink-paths.sh'
'symlink-paths.sh',
'debugger.sh'
],
'workdir': meson.current_source_dir(),
}

0 comments on commit 1293388

Please sign in to comment.