쉘이 스크립트에서 NUL 바이트를 무시하도록 허용됩니까?

쉘이 스크립트에서 NUL 바이트를 무시하도록 허용됩니까?

왜냐하면 그들 중 일부가 그렇게 하고 있기 때문입니다.

> echo echo Hallo, Baby! | iconv -f utf-8 -t utf-16le > /tmp/hallo
> chmod 755 /tmp/hallo
> dash /tmp/hallo
Hallo, Baby!
> bash /tmp/hallo
/tmp/hallo: /tmp/hallo: cannot execute binary file
> (echo '#'; echo echo Hallo, Baby! | iconv -f utf-8 -t utf-16le) > /tmp/hallo
> bash /tmp/hallo
Hallo, Baby!
> mksh /tmp/hallo
Hallo, Baby!
> cat -v /tmp/hallo
#
e^@c^@h^@o^@ ^@H^@a^@l^@l^@o^@,^@ ^@B^@a^@b^@y^@!^@
^@

이것이 실제로 호환성 문제입니까?필수의표준적으로? 꽤 위험하고 예상치 못한 것처럼 보이기 때문입니다.

답변1

~에 따르면POSIX,

입력 파일은 텍스트 파일이어야 하지만 줄 길이는 제한되지 않습니다.^

입력의 NUL 문자²텍스트가 아닌 것으로 만들어라, POSIX에 관한 한 동작은 지정되지 않으므로 sh구현은 원하는 대로 무엇이든 수행할 수 있습니다(그리고 POSIX와 호환됩니다).스크립트NUL을 포함할 수 없습니다).

일부 쉘은 처음 몇 바이트에서 0을 검색하고 실수로 스크립트가 아닌 파일을 실행하려고 한다고 가정하여 스크립트 실행을 거부합니다.

exec*p()이는 함수, env명령 sh, find -exec... 때문에 유용합니다 .필수의시스템이 ENOEXEC를 반환하면 명령을 해석하기 위해 쉘이 호출됩니다 execve(). 따라서 잘못된 아키텍처에 대한 명령을 실행하려는 경우 다음을 얻는 것이 가장 좋습니다.바이너리는 실행되지 않습니다.쉘에서 발생하는 파일 오류는 이를 쉘 스크립트로 이해하려는 쉘의 시도에서 발생하는 오류가 아닙니다.

이는 POSIX에서 허용됩니다.

실행 파일이 텍스트 파일이 아닌 경우 쉘은 이 명령 실행을 우회할 수 있습니다.

이 표준의 다음 개정판에서는로 변경됩니다:

쉘은 실행될 파일이 스크립트일 수 있는지 여부를 결정하기 위해 경험적 검사를 적용할 수 있으며, 파일이 스크립트일 수 없다고 판단되면 이 명령 실행을 우회할 수 있습니다. 이 경우 오류 메시지를 작성하고 종료 상태 126을 반환해야 합니다.
참고: 스크립트가 될 수 없는 파일을 거부하는 일반적인 경험적 방법은 고정 길이 내에서 <newline> 바이트 앞에 NUL 바이트 파일 접두사를 배치하는 것입니다. sh는 줄 길이가 무제한인 입력 파일을 허용해야 하므로 경험적 검사는 줄 길이를 기반으로 할 수 없습니다.

이 동작은 아카이브에 쉘 헤더와 바이너리 데이터가 포함되어 있더라도 쉘 자동 추출 아카이브를 방지할 수 있습니다.

쉘은 zsh입력에서 NUL을 지원하지만 NUL은 의 인수로 전달될 수 없으므로 execve()의 인수나 이름에만 사용할 수 있습니다.내장명령 또는 기능:

$ printf '\0() echo zero; \0\necho \0\n' | zsh | hd
00000000  7a 65 72 6f 0a 00 0a                              |zero...|
00000007

(여기서 NUL을 이름으로 사용하여 함수를 정의하고 호출하고 NUL 문자를 내장 echo명령에 대한 인수로 전달합니다.)

껍질을 벗겨내는 사람도 있는데, 그것도 현명한 일이다. NULs는 때때로 필러로 사용됩니다. 예를 들어, 터미널에서는 무시됩니다(캐리지 리턴(문자 그대로)과 같은 복잡한 제어 시퀀스를 처리할 시간을 갖기 위해 터미널로 전송되는 경우도 있습니다). 파일의 구멍은 NUL 등으로 채워지는 것처럼 보입니다.

텍스트가 아닌 것은 NUL 바이트로 제한되지 않습니다. 또한 로케일에서 유효한 문자를 형성하지 않는 바이트 시퀀스이기도 합니다. 예를 들어 0xc1 바이트 값은 UTF-8로 인코딩된 텍스트에 나타날 수 없습니다. 따라서 문자 인코딩으로 UTF-8을 사용하는 로케일에서 이러한 바이트를 포함하는 파일은 유효한 텍스트 파일이 아니므로 유효한 sh스크립트도 아닙니다.

사실, yash이것은 이와 같은 잘못된 입력에 대해 불평하는 내가 아는 유일한 쉘입니다.


1 이 표준의 다음 개정판에서는그것은 바뀔 것이다도착하다

입력 파일은 모든 유형이 될 수 있지만 셸 구문(XREF - XSH 2.10.2 셸 구문 규칙)에 따라 구문 분석될 파일의 ​​초기 부분은 문자로 구성되어야 하며 NUL 문자를 포함해서는 안 됩니다. 쉘은 줄 길이 제한을 적용해서는 안 됩니다.

자체 추출 아카이브를 설명하기 위해 나머지 부분에 NUL이 포함되어 있더라도 NUL 바이트 없이 유효한 구문 부분으로 시작하는 입력을 지원하려면 쉘이 명시적으로 필요합니다.

² 및 문자는 로케일의 문자 인코딩( 출력 참조 locale charmap)에 따라 디코딩되도록 되어 있으며, POSIX 시스템에서는 NUL 문자(인코딩이 항상 바이트 0임)는 인코딩에 바이트 0이 포함된 유일한 문자입니다. 즉, UTF-16은 POSIX 로케일에서 사용할 수 있는 문자 인코딩이 아닙니다.

LANG3 그러나 스크립트 내에서(예: // / 변수를 할당할 때) 로케일 변경 문제가 있으며 LC_CTYPE변경 사항이 입력을 해석하는 셸에 적용되는 경우가 있습니다.LC_ALLLOCPATH

답변2

이 동작의 이유는 약간 복잡합니다.

첫째, 최신 셸에는 널 바이트가 포함된 잠재적인 바이너리 파일에 대한 검사가 포함되어 있지만 이 검사는 파일의 첫 번째 줄만 확인합니다. 이것이 첫 번째 줄의 "#"이 동작을 변경하는 이유입니다. 역사적인 Bourne Shell에는 바이너리 검사가 없었고 언급한 방식으로 실행하는 데 "#"도 필요하지 않았습니다.

Bourne Shell에서 사용하는 특정 방법은 mbtowc()모든 널 바이트를 건너뛰는 방식으로 멀티바이트 문자를 지원합니다. mbtowc()널 바이트의 경우 문자 길이 0이 반환되어 루프가 다음 문자를 재시도하게 되기 때문입니다.

Bourne Shell은 1988년경에 이 코드를 도입했으며 다른 쉘도 이 동작을 복사했을 수 있습니다.

관련 정보