VYPR
Low severity3.3NVD Advisory· Published Apr 22, 2026· Updated May 4, 2026

CVE-2026-35378

CVE-2026-35378

Description

A logic error in the expr utility of uutils coreutils causes the program to evaluate parenthesized subexpressions during the parsing phase rather than at the execution phase. This implementation flaw prevents the utility from performing proper short-circuiting for logical OR (|) and AND (&) operations. As a result, arithmetic errors (such as division by zero) occurring within "dead" branches, branches that should be ignored due to short-circuiting, are raised as fatal errors. This divergence from GNU expr behavior can cause guarded expressions within shell scripts to fail with hard errors instead of returning expected boolean results, leading to premature script termination and breaking GNU-compatible shell control flow.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
coreutilscrates.io
< 0.8.00.8.0

Affected products

2
  • Uutils/Coreutilsreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • cpe:2.3:a:uutils:coreutils:*:*:*:*:*:rust:*:*range: <0.8.0

Patches

1
76b2f7877f55

expr: fix eager evaluation of parenthesized dead branches (#11395)

https://github.com/uutils/coreutilsCan BölükApr 3, 2026via ghsa
2 files changed · +27 23
  • src/uu/expr/src/syntax_tree.rs+2 21 modified
    @@ -624,9 +624,6 @@ pub struct AstNode {
     #[derive(Debug, Clone)]
     #[cfg_attr(test, derive(Eq, PartialEq))]
     pub enum AstNodeInner {
    -    Evaluated {
    -        value: NumOrStr,
    -    },
         Leaf {
             value: MaybeNonUtf8String,
         },
    @@ -650,15 +647,6 @@ impl AstNode {
             Parser::new(input).parse()
         }
     
    -    pub fn evaluated(self) -> ExprResult<Self> {
    -        Ok(Self {
    -            id: get_next_id(),
    -            inner: AstNodeInner::Evaluated {
    -                value: self.eval()?,
    -            },
    -        })
    -    }
    -
         pub fn eval(&self) -> ExprResult<NumOrStr> {
             // This function implements a recursive tree-walking algorithm, but uses an explicit
             // stack approach instead of native recursion to avoid potential stack overflow
    @@ -669,9 +657,6 @@ impl AstNode {
     
             while let Some(node) = stack.pop() {
                 match &node.inner {
    -                AstNodeInner::Evaluated { value, .. } => {
    -                    result_stack.insert(node.id, Ok(value.clone()));
    -                }
                     AstNodeInner::Leaf { value, .. } => {
                         result_stack.insert(node.id, Ok(value.to_owned().into()));
                     }
    @@ -896,9 +881,7 @@ impl<'a, S: AsRef<MaybeNonUtf8Str>> Parser<'a, S> {
                     value: self.next()?.into(),
                 },
                 b"(" => {
    -                // Evaluate the node just after parsing to we detect arithmetic
    -                // errors before checking for the closing parenthesis.
    -                let s = self.parse_expression()?.evaluated()?;
    +                let s = self.parse_expression()?;
     
                     match self.next() {
                         Ok(b")") => {}
    @@ -1070,9 +1053,7 @@ mod test {
                 AstNode::parse(&["(", "1", "+", "2", ")", "*", "3"]),
                 Ok(op(
                     BinOp::Numeric(NumericOp::Mul),
    -                op(BinOp::Numeric(NumericOp::Add), "1", "2")
    -                    .evaluated()
    -                    .unwrap(),
    +                op(BinOp::Numeric(NumericOp::Add), "1", "2"),
                     "3"
                 ))
             );
    
  • tests/by-util/test_expr.rs+25 2 modified
    @@ -217,6 +217,29 @@ fn test_and() {
         new_ucmd!().args(&["", "&", ""]).fails().stdout_only("0\n");
     }
     
    +#[test]
    +fn test_parenthesized_short_circuit_dead_branches() {
    +    new_ucmd!()
    +        .args(&["1", "|", "(", "1", "/", "0", ")"])
    +        .succeeds()
    +        .stdout_only("1\n");
    +
    +    new_ucmd!()
    +        .args(&["0", "&", "(", "1", "/", "0", ")"])
    +        .fails_with_code(1)
    +        .stdout_only("0\n");
    +
    +    new_ucmd!()
    +        .args(&["1", "|", "(", "0", "&", "(", "1", "/", "0", ")", ")"])
    +        .succeeds()
    +        .stdout_only("1\n");
    +
    +    new_ucmd!()
    +        .args(&["0", "&", "(", "1", "|", "(", "1", "/", "0", ")", ")"])
    +        .fails_with_code(1)
    +        .stdout_only("0\n");
    +}
    +
     #[test]
     fn test_length_fail() {
         new_ucmd!().args(&["length", "αbcdef", "1"]).fails();
    @@ -580,11 +603,11 @@ fn test_num_str_comparison() {
     }
     
     #[test]
    -fn test_eager_evaluation() {
    +fn test_missing_closing_parenthesis_reports_syntax_error() {
         new_ucmd!()
             .args(&["(", "1", "/", "0"])
             .fails()
    -        .stderr_contains("division by zero");
    +        .stderr_contains("expecting ')' after '0'");
     }
     
     #[test]
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.