Đây là một lỗi nhỏ, rất cũ, và vẫn đang được dùng trong code mới. Nó đáng một bài riêng vì cách sửa sai còn nguy hiểm hơn cách không sửa.
API này để làm gì
`wordexp(3)` là API POSIX để thực hiện mở rộng kiểu shell từ C. Người ta hay dùng nó cho một việc rất nhỏ: đổi `~/foo` thành `/home/me/foo`.
Nhưng tham số `0` ở cuối hàm là flags, và mặc định **mọi tính năng mở rộng đều bật** — kể cả command substitution. Nghĩa là nếu input đến từ người dùng, `$(whoami)/foo` sẽ chạy `whoami`; `$(cat /etc/passwd)/foo` sẽ đọc file passwd và trả về nội dung đó trong đường dẫn.
Cách sửa hiển nhiên không đủ
Tắt command substitution cần truyền `WRDE_NOCMD`. Nhưng trên hai libc phổ biến, đây chỉ là giải pháp tạm:
- musl triển khai `wordexp(3)` bằng cách gọi shell hệ thống. Nó có quét sơ qua các mẫu command substitution trước khi gọi shell, nhưng bỏ sót một số mẫu mà shell bên dưới (GNU Bash) vẫn coi là hợp lệ.
- macOS libc cũng gọi ra ngoài, qua `/usr/lib/system/wordexp-helper`. Theo Julian Squires, lỗi bypass tương tự vẫn còn tính đến năm 2023.
- Nghĩa là: ngay cả khi bạn đã làm đúng theo tài liệu, input không tin cậy vẫn có thể thoát ra shell.
Nên làm gì
- Không dùng `wordexp(3)` trên input không tin cậy — kể cả khi đã truyền `WRDE_NOCMD`.
- Nếu chỉ cần mở rộng `~`, tự viết: so khớp tiền tố `~/` rồi nối với `$HOME`. Đó là vài dòng và không gọi shell.
- Nếu cần mở rộng biến môi trường, dùng `getenv` với danh sách biến cho phép, không dùng API thực hiện mở rộng shell tổng quát.
- Coi `wordexp(3)` như `system(3)`: một API thực thi lệnh, không phải một hàm xử lý chuỗi.
Bài gốc là một TIL ngắn, đáng đọc nguyên văn vì có code mẫu cho cả ba trường hợp. Nguồn: POSIX wordexp(3) is dangerous, spec wordexp, musl wordexp.c, Julian Squires về wordexp.