libc에서 이 함수를 보고 대체 왜 들어 있는지 의문이 들었음. 셸 자체는 이를 구현에 사용하지 않으며, 틸드 확장은 훨씬 단순한 알고리즘으로 구현 가능함. 셸을 fork해서 실행하는 방식이라면 엄청나게 위험하고, 셸 자체에서 사용하기도 불가능해짐.
근본적으로 명령 치환 안에는 셸 언어 전체가 들어갈 수 있으므로, 이 함수의 명세는 사실상 셸 전체를 포괄함. $( f() { echo 'defined function'; }; f; f && f; f | f )처럼 함수 정의와 호출, 조건부 실행, 파이프까지 들어갈 수 있음. libc 안에 셸 전체를 구현할 리도 없으니, 이 함수는 libc에서 제거하는 편이 맞아 보임. 누가 왜 넣었는지 모르겠음.
완전한 sh나 C 어휘 분석기의 존재를 직간접적으로 전제하는 POSIX API가 꽤 있음. 하지만 libc에서 이런 분석기를 재사용할 수 있도록 구성된 POSIX 시스템은 거의 없어, 결국 이런 무리한 우회 구현이 생겨남.
링크한 글에서는 사람들이 glob에 계속 더 많은 기능을 요구하자, 위원회가 모든 요구를 포괄하려고 지나치게 광범위한 wordexp를 추가한 것 같다고 풀이함. 출처는 https://www.cipht.net/2023/11/21/worried-by-wordexp.html 임.
참고로 glibc는 명령 치환에만 시스템 셸을 사용하고, 나머지 단어 확장 로직은 직접 구현함.
“내가 이 결정에 관여할 수 있는 한, wordexp()는 OpenBSD의 libc에 절대 들어가지 않을 것임. 이를 거부하는 오픈소스 프로젝트가 하나라도 있다면 개발자들이 계속 사용을 피할지도 모름.” — Theo de Raadt, 2010년.
wordexp(3) 에는 다른 위험도 있음. 보통 셸을 fork하므로 멀티스레드 프로그램에서 사용할 수 없음.
glibc는 확장 기능을 내장하고 있지만, 변수를 설정하는 셸 확장은 스레드 안전하지 않은 setenv() 를 호출함. 이 때문에 전통적인 fork 방식과 달리 호출자의 환경까지 변경됨. libc는 원래 setenv를 거의 호출하지 않으므로 특히 이례적임. 문서에 큼직하게 “사용하지 말 것”이라는 경고를 붙여야 할 함수임.
Hare 표준 라이브러리에 wordexp:: 모듈을 추가할지 논의하며 조사했을 때 같은 결론에 도달함. 신뢰할 수 없는 입력에 안전하게 사용할 수 없고, 제대로 구현한 곳도 거의 없어 강하게 반대했음. musl 같은 구현에서는 셸 우회 취약점을 쉽게 찾을 수 있고, 명령 치환 외에는 시스템 셸을 쓰지 않는 glibc 같은 구현은 $(case 0 in *) echo hi; esac) 같은 경계 사례를 처리하지 못함.
결국 POSIX를 따르지 않는 쪽으로 타협함. Hare의 wordexp는 POSIX 셸인지와 무관하게 항상 시스템 셸을 사용하고, WRDE_NOCMD를 아예 구현하지 않음. 대신 “설계상 사용자 입력의 임의 셸 명령을 실행하므로, 신뢰할 수 있는 환경에서만 사용해야 함”이라고 명시함. 실제로 안전하게 만들어 주지 않는 플래그는 보안에 대한 잘못된 확신을 주므로 없는 것보다 나쁨.
주된 용도는 도입 예정인 hare.ini 설정 형식에서 의존성과 링커 플래그 등을 선언하면서 pkg-config도 사용할 수 있게 하는 것이었음. 내 첫 선택은 wordexp가 아니었겠지만, 최종 타협안에는 만족함.
이런 현상을 파서 간 해석 차이(parser differential) 라고 부르려 함. 웹 방화벽이 HTML을 잘못 해석해 차단하는 맥락에서 자주 쓰이는 용어지만, 셸에서도 아주 자주 보임. 셸 구문 분석이 어렵다 보니 불완전한 두 번째 파서를 만들기 때문임. https://nhimg.org/glossary/parser-differential/.
bash조차 30년이 지나도록 여러 구문을 제대로 분석하지 못하며, 이것도 https://oils.pub/ 를 시작한 이유 중 하나임. 당장 버그 목록은 없지만, 중첩된 () {} $() ${}를 분석하는 것부터 극도로 어려움. 짝이 맞지 않는 case 0 in *) 문법은 그 이유 중 하나일 뿐임.
누군가에게 위험한 기능이 다른 누군가에게는 유용할 수 있음. i3의 include 설정 지시문은 wordexp를 사용하며, 사용자 안내서에도 include ~/.config/i3/config.d/*.conf 같은 와일드카드 확장과 include ~/.config/i3/hostname.conf 같은 명령 치환 예제가 있음. https://i3wm.org/docs/userguide.html#include.
필요 없는 기능을 직접 끄지 않으면 원치 않는 기능까지 활성화되는 것은 좋지 않은 API 설계임. 여기에 일부 libc의 버그도 문제가 되는 듯함.
특히 lines에 if (flags & WRDE_NOCMD) for (i=0; s[i]; i++) switch (s[i]) { 같은 코드가 있는 libc에는 선뜻 의존하고 싶지 않음.