Skip to content

Feature suggestion: Warnings regarding (automatic) recursive indirection/eval within [[ (( and $(( blocks #3067

@fedy-cz

Description

@fedy-cz

Came across this (poorly documented) feature of bash arithmetic expansion within the [[ , (( and $(( blocks (my quick interpretation):

Within the arithmetic expansion in these blocks all variables are recursively expanded (using indirection) until the content can be parsed as an integer or no further indirection is possible. This includes evaluating arithmetic expressions (contained as a string within the variable).

Example:

#!/bin/bash
a=b
b=c
c=10
if [[ $a -gt 5 ]] ; then echo Greater ; else "Not greater"  ; fi

Outputs:

Greater

Side note: Interestingly shellcheck complains about b and c being unused here, which in fact is not true :)

This "feature" is discussed (for example) in this bash mailing list thread:
https://lists.gnu.org/archive/html/help-bash/2018-10/msg00011.html

Definitely an unexpected behavior (in my book), and very poorly documented. This could unearth a whole new class of bugs.

The instance I personally came across (simplified):

#!/bin/bash

default=10

use_input() {
  return 0
}

read_config() {
  local input
  input=$( < filename )
  echo "Read: $input"
  if use_input ; then
    # Notice the missing $ there
    global_setting=input
  else
    global_setting=$default
  fi

  # Final check
  if [[ $global_setting -gt 0 ]] ; then
    echo "global setting is numeric"
  else
    exit 1
  fi
}
read_config

echo "global setting: $global_setting"

When I put value 5 into filename this outputs:

Read: 5
global setting is numeric
global setting: input

Few other simple examples I can think off:

#!/bin/bash
# Somewhere:
x=5
var=x
# ...
# Later, attempt to make sure that a variable is a positive numeric value
if [[ var -gt 0 ]] ; then
  echo "var is numeric"
  # But in fact it's just x
fi

Or:

#!/bin/bash
internal_val=5
var=$( < file )
# file contains the string internal_val
if [[ var -gt 0 ]] ; then
  # expecting to be processing value from file here but instead working with the internal_val
  x = $((  var * 5 ))
fi

You can even put whole expressions in there as mentioned in the referred mailing list thread:

#!/bin/bash
one=1
two=2
x='one + two'
code='4 > 2 ? x : y'
# This  just evals
echo $(( code ))

The main pain points I see right now are:

  • unintended aliasing / recursive indirection & it's effects
  • incorrect input validation issues

Wouldn't be surprised if this led to a few security incidents as well.

Generally:

  • Can hide an accidental "variable name" string assignment instead of a variable value (missing $).
  • Anything that comes from user input should avoid being used within the (( , $(( and arithmetic part of [[ blocks unless properly validated / pattern matched first.
  • Never use these block for input/value format validation (one exception being [[ containing no arithmetic expression that uses the input).

Not sure how well this could translate into actual shellcheck rules. But kicking off the discussion.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions