Как безопасно использовать ассоциативные массивы внутри арифметических выражений?

Скажите df, что выводить:

df -h --output=avail | tail -n1

Note: this might not be portable to all *nixes.

16
04.01.2021, 17:12
1 ответ

Проблема заключается в том, что в арифметическом выражении оболочки, например, внутри$((...))(POSIX )или((...))(ksh/bash/zsh ), индексов массива или аргументов некоторых встроенных функций оболочки или [[...]]операнды, разложения слов (${param}, $((...)), $[...], $(...), `...`,${...; })выполняются сначала , а затем полученный текст интерпретируется как арифметическое выражение.

В случае $((...))это даже требование POSIX.

Это позволяет работать таким вещам, как op=+; echo "$(( 1 $op 2 ))", и это объясняет, почему a=1+1; echo "$(($a * 2))"выводит 3вместо 4, поскольку вычисляется выражение 1+1 * 2.

Это также частично почему использование непроверенных данных в арифметических выражениях является уязвимостью системы безопасности в целом.

Что легко упустить из виду, так это то, что это также применимо к таким вещам, как

(( assoc[$var]++ ))

Выше, за исключением ksh93, сначала раскрывается $var, а результат интерпретируется.

Это означает, что если $varсодержит @или *, то вычисляются выражения assoc[@]++или assoc[*]++, и @/ *имеют там особое значение. Если $varравно x] + 2 + assoc[y, это становится assoc[x] + 2 + assoc[y].

Теперь обычно, в $(( $var )), даже если $varсодержит что-то вроде $(reboot), второго раунда расширения не происходит, rebootне будет запускаться. Но, как уже было замечено в Последствия для безопасности использования непроверенных данных в арифметической оценке Shell , есть исключение, если оно появляется внутри word[...], чтобы разрешить рекурсивное расширение. В основе проблемы лежит неудачная особенность оболочки Korn, согласно которой, если varсодержит арифметическое выражение, то в $((var))арифметическое выражение в $varвычисляется, даже рекурсивно (например, когда var2='var3 + 1' var='var2 + 1'), что-то разрешено, но не требуется POSIX.

Поскольку это распространяется на элементы массива, это означает, что содержимое индексов массива в конечном итоге оценивается рекурсивно. Итак, если $varравно $(reboot),затем (( assoc[$var]++ ))вызывает reboot.

ksh93, кажется, имеет некоторый уровень работы, но только тогда, когда $varне содержит $. Итак, хотя ksh93 работает с var=']', var='@'или var='`reboot`', это не так с $(reboot).

Например, если мы заменим rebootбезвреднымuname>&2:

$ var='1$(uname>&2)' ksh -c 'typeset -A a; (( a[$var]++ )); typeset -p a'
Linux
typeset -A a=([1]=1)
$ var='1$(uname>&2)' bash -c 'typeset -A a; (( a[$var]++ )); typeset -p a'
Linux
Linux
declare -A a=([1]="1" )
$ var='1$(uname>&2)' zsh -c 'typeset -A a; (( a[$var]++ )); typeset -p a'
Linux
Linux
typeset -A a=( [1]=1 )

Команда unameв конечном итоге выполняется (дважды в bashи zsh, я полагаю, один раз для получения текущего значения и второй раз для выполнения назначения ).

В версии 5.0 в bash добавлен параметр assoc_expand_once, который изменяет поведение:

$ var='1$(uname>&2)' bash -O assoc_expand_once -c 'typeset -A a; ((a[$var]++)); typeset -p a'
declare -A a=(["1\$(uname>&2)"]="1" )

теперь в порядке, но не устраняет проблемы с символами @, *или ], поэтому не устраняет уязвимость выполнение произвольной команды :

$ var='x]+b[1$(uname>&2)' bash -O assoc_expand_once -c 'typeset -A a; ((a[$var]++)); typeset -p a'
Linux
declare -A a

(на этот раз unameвыполняется как часть оценки простого массива(b)оценки индекса ).

Список проблемных символов зависит от оболочки. $— задача для всех трех, \, `, [и ]— задача для bashи zsh, ", '— для bash. То же самое для @и *и пустых значений. Также обратите внимание, что в некоторых локалях кодировка некоторых символов действительно содержит кодировку \, [или ], по крайней мере, и может вызвать проблемы. Как их избежать, нужно делать по-разному во всех трех оболочках.

Чтобы обойти это, можно:

assoc[$var]=$(( ${assoc[$var]} + 1 ))

вместо этого. То есть:

  1. не выполнять присваивание члену ассоциативного массива как части арифметического выражения, а выполнять только голые присваивания члену ассоциативного массива. Другими словами, не используйте =, ++, --, +=, /=... арифметические операторы с членом ассоциативного массива в качестве цели.
  2. при ссылке на ассоциативный массив в арифметическом выражении используйте не assoc[$var], а${assoc[$var]}(или $assoc[$var]в zsh),или (${assoc[$var]}), если это должно содержать арифметическое выражение, а не просто число.

Но, как всегда, значение этого члена ассоциативного массива должно находиться под вашим контролем, желательно простое число, и, как и для любого другого раскрытия параметра, предпочтительно помещать вокруг него пробелы. Например, ((1 - $var))предпочтительнее ((1-$var)), так как последнее вызовет проблемы с отрицательными значениями.(((1--1))вызывает синтаксическую ошибку в некоторых оболочках, так как это оператор --, применяемый к 1.

Другое предостережение заключается в том, что когда $varпусто, в (( 1 + var ))varпо-прежнему является токеном в синтаксисе арифметического выражения и соответствующим значением, если 0. Но в (( 1 + $var ))арифметическое выражение становится 1 +, что является синтаксической ошибкой.((( $var + 1 ))в порядке, хотя это становится + 1, вызывая унарный оператор +).

Другие подходы с bash(, когда для параметра assoc_expand_onceустановлено не включено )или zsh(, но не ksh93, что по-прежнему имеет проблему с символами ]и \. ), заключаются в том, чтобы отложить расширение до той второй, рекурсивной интерпретации, упомянутой выше.

  • (( assoc[\$var]++ ))
  • let 'assoc[$var]++'(обязательно используйте одинарные кавычки здесь)
  • incr='assoc[$var]++'; (($incr))(или даже((incr)))
  • ((' assoc[$var]++ '))или(( assoc['$var']++ ))(bashтолько ).

Те имеют преимущество или сохраняют статус выхода, полученный в результате арифметической оценки(успех , если не -ноль ), поэтому можно делать такие вещи, как:

if (( assoc[\$var]++ )); then
  printf '%s\n' "$var was already seen"
fi

Остается одна проблема, связанная с bashоболочкой.:bashАссоциативные массивы не поддерживают пустые ключи. В то время как assoc[]=xне работает как в bash, так и в zsh(, а не в ksh93), assoc[$var], когда $varпусто, работает в zshили ksh93, но не bash. Даже zshassoc+=('' value), теперь поддерживаемый bash -5.1, не работает в bash.

Итак, если вы работаете конкретно с bashи если пустой ключ является одним из возможных значений,единственный вариант — добавить фиксированный префикс/суффикс. Так что используйте, например,:

assoc[.$var]=$(( ${assoc[.$var]} + 1 ))

Или:

let 'assoc[.$var]++'
(( assoc[.\$var]++ ))
...
20
18.03.2021, 22:38

Теги

Похожие вопросы