Как вы можете использовать чистую unset shell builtin? Можете ли вы написать сценарии оболочки, которые невосприимчивы к подделке?

Я хочу сказать, что я хочу использовать unset, который не является самой функцией оболочки. Если бы я мог это сделать, я мог бы убедиться, что command чист, запустив

#!/bin/sh
{ \unset -f unalias command [; \unalias unset command [ } 2>/dev/null;
# make zsh find *builtins* with `command` too:
[ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on

Если я использую Debian Almquist shell (тире), я думаю, что могу полагаться, что \unset является чистым. По крайней мере, я не мог определить функцию оболочки с именем unset в dash. Если в bash или в zsh я мог бы определить unset() { echo fake unset; }, и после этого я не могу отключить функцию: \unset -f unset выводит "fake unset".

Относительно этого в bash script можно экспортировать функцию export -f <function name>, чтобы ее можно было использовать в сценариях bash, вызываемых script. Однако то же самое не работает в сценариях dash. Интересно, если мне приходится беспокоиться о том, что команда определяется как функция оболочки вне файла script, который я пишу, если я использую dash? Как насчет других совместимых с POSIX оболочек?

Ответ 1

Примечание. Следующее относится ко всем основным POSIX-совместимым оболочкам, за исключением случаев, когда указано иначе: bash, dash, ksh и zsh. (dash, оболочка Almquist Debian, является оболочкой по умолчанию (sh) на дистрибутивах на базе Debian, таких как Ubuntu).

  • unset имеет свое первоначальное значение - встроенный, который может деинфицировать функции оболочки с помощью параметра -f - это ключ для обеспечения того, что любое другое ключевое слово, команда или встроенный ключ оболочки имеет свое первоначальное значение.

    • Начиная с немодифицированного unset, вы можете обеспечить немодифицированный shopt и/или command, и вместе они могут использоваться для обхода или деинфиниции любых псевдонимов или функций оболочки, которые могут теневые ключевые слова оболочки, встроенные функции и внешние утилиты.
    • В качестве альтернативы неопределенным функциям command можно использовать для обхода их, включая те, которые могут быть определены вне вашего кода, через среду.
      экспортирующие функции, поскольку поддерживается только bash, является только одним из этих механизмов; разные оболочки имеют разные и могут поддерживать несколько - см. ниже.
  • Только dash, ksh и bash, когда в режиме совместимости с POSIX гарантируется, что unset не было переопределено:

    • dash и ksh являются безопасными, поскольку они не позволяют определить функцию с именем unset, как вы обнаружили, и любую форму псевдонима можно обойти, вызвав как \unset.

    • bash, когда в режиме совместимости с POSIX вы можете определить функцию с именем unset, но игнорируете ее при вызове unset и всегда выполняете встроенную функцию, как вы позже ее обнаружили.

      • Учитывая, что режим POSIX совместимости ограничивает набор Bash и изменяет его поведение, обычно не желательно запускать в нем свой Bash код. В в нижней части этого сообщения представлена ​​реализация обходного пути, которую вы предлагаете, которая временно активирует режим совместимости POSIX, чтобы гарантировать, что функция unset не определена. li >
  • К сожалению, насколько я знаю, в zsh - а также в режиме bash по умолчанию - существует , чтобы гарантировать, что unset сам hasn 't переопределены, и могут быть другие POSIX-подобные оболочки, которые ведут себя аналогично.

    • Вызов \unset (со ссылкой на любую часть имени) обходит переопределение псевдонимов, но не переопределяет функцию - и отменить то, что вам потребуется оригинальный unset: catch 22.
  • Таким образом, без контроля над средой исполнения, вы не можете писать сценарии оболочки, которые полностью защищены от несанкционированного доступа, , если вы не знаете, что ваш код будет выполнен dash, ksh или bash (с обходным решением на месте).

    • Если вы согласны с тем, что unset не был изменен, наиболее надежный подход:

      • Используйте \unset -f, чтобы гарантировать, что unalias и command немодифицированы (не затенены функцией оболочки: \unset -f unalias command)

        • Функции, в отличие от псевдонимов, должны быть явно undefined по имени, но не все оболочки предоставляют механизм для перечисления всех определенных функций, к сожалению (typeset -fработает в bash, ksh и zsh, но dash не имеет никакого механизма вообще), так что невозможно определить все функции не всегда.
      • Используйте \unalias -a, чтобы удалить все псевдонимы.

      • Затем вызовите все с помощью command [-p], за исключением функций, которые вы определили. При вызове внешних утилит используйте явные пути, когда это возможно, и/или, в случае стандартных утилит, используйте command -p, который использует минимальное определение $PATH, ограниченное стандартными местоположениями (запустите command -p getconf PATH, чтобы увидеть это определение).


Дополнительная информация:

  • В POSIX, цитируя любую часть имени команды (например, \unset), обходит любую форму псевдонима или форму ключевого слова (зарезервированное слово в POSIX и zsh parlance) этим именем - но не функциями оболочки.

  • В POSIX, unalias -a undefine all aliases. Нет эквивалента, совместимой с POSIX командой для определения всех функций.

    • Предостережение: более старые версии zsh не поддерживают -a; по крайней мере, v5.0.8, однако, они делают.
  • Встроенный command может использоваться для обхода ключевых слов, псевдонимов, функций в bash, dash и ksh - другими словами: command выполняет только встроенные и внешние утилиты. Напротив, zsh по умолчанию также обходит встроенные функции; чтобы сделать zsh выполнение встроенных команд, используйте options[POSIX_BUILTINS]=on.

  • Для выполнения внешних утилит с именем <name> только для всех оболочек можно использовать следующее:
    "$(command which <name>)" ...
    Обратите внимание: хотя which не является утилитой POSIX, он широко доступен на современных Unix-подобных платформах.

  • Приоритет командных форм:

    • bash, zsh: alias > shell keyword > shell function > встроенная > внешняя утилита
    • ksh, dash: shell keyword > alias > shell function > встроенная > внешняя утилита
    • I.e.: В bash и zsh псевдоним может переопределить ключевое слово оболочки, а в ksh и dash он не может.
  • bash, ksh и zsh - но не dash - все позволяют использовать нестандартную сигнатуру функции function <name> { ... в качестве альтернативы POSIX-совместимой форме <name>() { ....

    • Синтаксис function является обязательным условием для:
      • гарантируя, что <name> сам не подвержен расширению псевдонимов до того, как будет определена функция.
      • возможность выбрать <name>, который также является ключевым словом для оболочки;
        обратите внимание, что такую ​​функцию можно вызвать только в кавычковой форме; например, \while.
      • (В случае ksh, используя синтаксис function, дополнительно подразумевает, что операторы typeset создают локальные переменные.)
    • dash, ksh и bash, когда в режиме POSIX дополнительно запрещаются функции именования для специальных встроенных функций (например, unset, break, set, shift); список специальных встроенных функций, определенных POSIX, можно найти здесь; как dash и ksh добавить еще несколько, которые нельзя переопределить (например, local в dash; typeset и unalias в ksh), но обе оболочки имеют дополнительные, неспецифические встроенные которые могут быть переопределены (например, type).
      Обратите внимание, что в случае ksh указанные правила применяются независимо от того, используется ли синтаксис function или нет.
  • Потенциальные источники функций оболочки среды в области вашего кода:

    • Примечание. Самый простой способ защитить это - использовать (немодифицированный) command встроенный (в zsh с options[POSIX_BUILTINS]=on, чтобы предотвратить обход встроенных функций), когда вы хотите позвонить встроенная или внешняя утилита.

    • POSIX требует, чтобы script, указанный его абсолютным путем в переменной окружения ENV, был получен для интерактивных оболочек (с некоторыми ограничениями - см. спецификация); ksh и dash всегда чтят это, тогда как bash делает это только при вызове sh или, в v4.2 +, с --posix; напротив, zsh никогда не выполняет эту переменную.

      • Примечание. Ваш код, запускаемый как script, обычно запускается в неинтерактивной оболочке, но это не гарантируется; например, ваш код может быть получен из интерактивного script, или кто-то может вызвать ваш script с помощью, например, sh -i, чтобы заставить интерактивный экземпляр.
    • bash имеет 2 механизма:

      • Экспорт отдельных функций с помощью export -f или declare -fx (другие оболочки поддерживают только экспортирующие переменные)
      • Указание полного пути script к источнику при запуске неинтерактивной оболочки в необязательной переменной среды BASH_ENV.
    • ksh поддерживает автоматическую загрузку функций через необязательную переменную среды FPATH: файлы, содержащие определения функций, расположенные в любом каталоге, указанном в FPATH, неявно и автоматически загружаются.

      • (zsh тоже поддерживает FPATH, но для функций автоматической загрузки требуется явный оператор autoload <name>, поэтому, если вы специально не запрашиваете функцию с заданным именем для автоматической загрузки, никакие функции не будут добавлены в ваш оболочки.)
    • zsh поддерживает сценарии поиска для любого экземпляра zsh (будь то интерактивного или нет) через его файлы /etc/zshenv и ~/.zhsenv.

    • (dash, похоже, не поддерживает какой-либо механизм определения функций через среду.)


Обходной путь для bash: убедитесь, что unset имеет свое первоначальное значение:

Это обходное решение безопасно, если вы знаете, что bash будет выполнять ваш script, который, к сожалению, не может быть гарантирован.

Кроме того, поскольку он изменяет среду оболочки (удаление псевдонимов и функций), она не подходит для сценариев, которые предназначены для источника.

Как указано, обычно не желательно запускать ваш код в режиме совместимости с Bash POSIX, но вы можете временно активировать его, чтобы гарантировать, что unset не затеняется функцией:

#!/bin/bash

# *Temporarily* force Bash into POSIX compatibility mode, where `unset` cannot 
# be shadowed, which allows us to undefine any `unset` *function* as well
# as other functions that may shadow crucial commands.
# Note: Fortunately, POSIXLY_CORRECT= works even without `export`, because
#       use of `export` is not safe at this point.
#       By contrast, a simple assignment cannot be tampered with.
POSIXLY_CORRECT=

# If defined, unset unset() and other functions that may shadow crucial commands.
# Note the \ prefix to ensure that aliases are bypassed.
\unset -f unset unalias read declare

# Remove all aliases.
# (Note that while alias expansion is off by default in scripts, it may
#  have been turned on explicitly in a tampered-with environment.)
\unalias -a  # Note: After this, \ to bypass aliases is no longer needed.

# Now it is safe to turn POSIX mode back off, so as to reenable all Bash
# features.
unset POSIXLY_CORRECT

# Now UNDEFINE ALL REMAINING FUNCTIONS:
# Note that we do this AFTER switching back from POSIX mode, because
# Bash in its default mode allows defining functions with nonstandard names
# such as `[` or `z?`, and such functions can also only be *unset* while
# in default mode.
# Also note that we needn't worry about keywords `while`, `do` and `done`
# being shadowed by functions, because the only way to invoke such functions
# (which you can only define with the nonstandard `function` keyword) would
# be with `\` (e.g., `\while`).
while read _ _ n; do unset -f "$n"; done < <(declare -F)

# IN THE REST OF THE SCRIPT:
#  - It is now safe to call *builtins* as-is.
#  - *External utilities* should be invoked:
#      - by full path, if feasible
#      - and/or, in the case of *standard utilities*, with
#        command -p, which uses a minimal $PATH definition that only
#        comprises the locations of standard utilities.
#      - alternatively, as @jarno suggests, you can redefine your $PATH
#        to contain standard locations only, after which you can invoke
#        standard utilities by name only, as usual:
#          PATH=$(command -p getconf PATH)

# Example command:
# Verify that `unset` now refers to the *builtin*:
type unset

Команда тестирования:

Предположим, что приведенный выше код был сохранен в файле script в текущем каталоге.

Следующая команда имитирует среду с несанкционированным доступом, где unset затеняется как псевдонимом, так и функцией, а файл script является источником, что приводит к тому, что он видит эту функцию и, при интерактивном доступе, расширяет псевдоним тоже:

$ (unset() { echo hi; }; alias unset='echo here'; . ./script)
unset is a shell builtin

type unset вывод unset is a shell builtin является доказательством того, что и функция, и алиас, затеняющий встроенный unset, были деактивированы.

Ответ 2

достаточно забавно, вы уже сказали, что встроенное имя - command

$ var="FOO"
$ unset() { echo nope; }
$ echo "${var}"
FOO
$ unset var
nope
$ echo "${var}"
FOO
$ command unset var
$ echo "${var}"
<nothing!>

это не поможет, если вы находитесь во враждебной среде, где кто-то создал функцию command() { :; }. но если вы находитесь в агрессивной среде, вы уже потеряли;).

когда дело доходит до экспорта функций в среду, это bash -специфическое расширение, и вы не должны действительно полагаться на это. Оболочки POSIX (например, тире) не поддерживают это по дизайну.

Ответ 3

Вот что я знаю, что можно сделать...

#!/bin/bash --posix

# if e.g. BASH_FUNC_unset() env variable is set, script execution cannot
# get this far (provided that it is run as is, not as `bash script ...`)

unset -f builtin command declare ...

saved_IFS=$IFS; readonly saved_IFS
# remove all functions (shell builtin declare executed in subshell)
IFS=$'\n'; for f in `declare -Fx`; do unset -f ${f##* }; done; IFS=$saved_IFS