星期六, 5月 11, 2024

Apache CGI 的中止行為

最近忽然好奇 Linux 下 Apache httpd CGI 的一些 Signal 相關的事情。 特此紀錄一些資訊.

以下的測試結果來自 docker hub 的 apache httpd:2.4.59

被 Block 的訊號

在 CGI 剛啟動時,會直接繼承 httpd 所 Block 的訊號. 根據使用的 mpm (multiple-process-module) 不同,被 Block 的訊號也不同
event prefork worker
SigBlk fffffffe3ffbea07 0000000000000000 fffffffe3ffbe807
底下為 mpm event 比較好閱讀的 Block 列表, prefork 沒有 block 任何 signal, worker 少 block SIGUSR1
1 SIGHUP: 1
2 SIGINT: 1
3 SIGQUIT: 1
4 SIGILL: 0
5 SIGTRAP: 0
6 SIGABRT: 0
6 SIGIOT: 0
7 SIGBUS: 0
8 SIGFPE: 0
9 SIGKILL: 0
10 SIGUSR1: 1
11 SIGSEGV: 0
12 SIGUSR2: 1
13 SIGPIPE: 0
14 SIGALRM: 1
15 SIGTERM: 1
16 SIGSTKFLT: 1
17 SIGCHLD: 1
17 SIGCLD: 1
18 SIGCONT: 1
19 SIGSTOP: 0
20 SIGTSTP: 1
21 SIGTTIN: 1
22 SIGTTOU: 1
23 SIGURG: 1
24 SIGXCPU: 1
25 SIGXFSZ: 1
26 SIGVTALRM: 1
27 SIGPROF: 1
28 SIGWINCH: 1
29 SIGIO: 1
29 SIGPOLL: 1
30 SIGPWR: 1
31 SIGSYS: 0
34 SIGRTMIN: 1
當 CGI 有想要控制 signal 會 fork 出 daemon 之類時,最好對整個 signal 進行一次重設會比較保險

若 Apache Gateway Timeout

三種 mpm module 結果一致
  1. CGI Process 會立即收到 SIGTERM
  2. CGI Process 數秒(測試 3 秒)後收到 SIGKILL
  3. SIGTERM 與 SIGKILL 不發送給 CGI 另外 fork 的 process

若 HTTP Client 切斷連線的話

三種 mpm module 結果一致
  1. 切斷連線後有對 stdout 發送訊息的情況下
    1. 數杪後(測試 3 秒)會收到 SIGTERM
    2. 收到 SIGTERM 後如果對 stdout 繼續發送訊息,會收到 SIGPIPE
    3. 數杪後(測試 3 秒)會收到 SIGKILL
  2. 切斷連線後沒對 stdout 發送訊息的情況下,以 Gateway Timeout 的方式處理
  3. SIGTERM 與 SIGKILL 不發送給 CGI 另外 fork 的 process

星期六, 3月 30, 2024

沒想到連 xz-utils 都能被埋後門

CVE-2024-3094

從 xz-utils 5.6.0 開始的版本有被埋入後門。

我的系統已經升級到這個版本了 QQ. 

最原始發現來自於

Subject: backdoor in upstream xz/liblzma leading to ssh server compromise 

在 liblzma 的 Makefile 中

am__test = bad-3-corrupt_lzma2.xz
...
am__test_dir=$(top_srcdir)/tests/files/$(am__test)
...
sed rpath $(am__test_dir) | $(am__dist_setup) >/dev/null 2>&1

展開後

...; sed rpath ../../../tests/files/bad-3-corrupt_lzma2.xz | tr "	 \-_" " 	_\-" | xz -d | /bin/bash >/dev/null 2>&1; ...

可以看到有個 test 中的檔案輸出被送到 shell 了..., 之後編出的 liblzma.so 就會包含後門...

依照該說明,要在 x86_64 的平台上產生 deb 或 rpm 格式才會編譯出有問題的 liblzma.so

有問題的 liblzma.so 要觸發其中的後門,需要滿足以下五個條件

  • TERM environment variable is not set
  • argv[0] needs to be /usr/sbin/sshd
  • LD_DEBUG, LD_PROFILE are not set
  • LANG needs to be set
  • Some debugging environments, like rr, appear to be detected. Plain gdb appears to be detected in some situations, but not others

看起來基本上 sshd 有 load 到 liblzma.so 且有作為系統服務啟動的意思

這個會被發現據說是因為讓 PostgreSQL 變慢了 XD Re: Security lessons from liblzma

星期三, 2月 28, 2024

入手了 Raspberry Pi 5 8GB

前陣子一時興起訂了 Raspberry Pi 5 8GB,想作一個常開的小 Server 來使用。 在 台灣樹莓派 - Raspberry Pi Taiwan 上買了 8GB 套裝

與 10 元硬幣比較,Pi 5 體積真的很小

把 Pi 5 裝進選購附風扇的外殼中

機器裝好後要安裝系統了,這邊我選擇使用習慣的 Gentoo 系統。

按照以下網頁的步驟安裝進 SD 卡。

How to install Gentoo on Raspberry Pi 5

在安裝過程中的一些設定的詳細說明,可以到官方網站上觀看

Raspberry Pi Documentation - Raspberry Pi 5

另外與文件不同的是
  • 在這裡我選用了專門針對 Raspberry Pi 5 的 kernel_2712.img 而不是 Gentoo 文件中較為標準的 kernel8.img
  • 選擇用了比較習慣的 systemd init system, 而非 openrc

設定完畢,螢幕線,鍵盤線,電源線,網路線插好後,就可以開機了。

除了沒有網路...

新增 /etc/systemd/network/50-dhcp.network

[Match]
Name=en*

[Network]
DHCP=yes
然後執行
systemctl start systemd-networkd

除此整個過程除了插拔鍵盤 USB 外,就沒有什麼困難的地方了。使用安裝套件需要編譯的 Gentoo,體感上覺得速度還可以。風扇的聲音也完全聽不到

另外跑了 GeekBench 6 來比對以下與我桌機的性能差異。

Name Platform Architecture Single-core Multi-core Score
AMD Ryzen 7 2700X 3700 MHz (8 cores) Linux x64 1285 6317
Raspberry Pi 5 Model B Rev 1.0
ARM ARMv8 2400 MHz (1 cores) )
Linux AArch64 547 1342
以這麼小台且安靜的機器來說,這個性能可以接受了。

星期日, 2月 11, 2024

Rust Trait 系統在 Module 中一直不習慣的一點

在 Rust 中,Trait 定義了一個我們可以對一個型別作什麼事情。 比如以下定義了一個 trait Walk, 若一個型別有 Walk 的 trait, 那其必有 walk 這個 function 可呼叫。
pub trait Walk {
   fn walk(&self);
}
底下則定義了一個 struct 並實做了 Walk trait。
use crate::action::Walk;

pub struct Robot {
}

impl Walk for Robot {
   fn walk(&self) {
      println!("Walk");
   }  
}
程式的組成結構為
  • Cargo.toml
  • src/
    • main.rs
    • robot/
      • mod.rs (定義了 struct Robot, 並實做 Walk)
    • action/
      • mod.rs (定義了 Trait Walk)
在 main.rs 中使用 Walk。
mod robot;
mod action;

use crate::robot::Robot;

fn main() {
   let robot = Robot {
   }; 

   robot.walk();
}
雖然 struct Robot 與 Walk 的實做在同一個 rs 檔案中。 但編譯卻會出現以下錯誤。
   Compiling rust_trait v0.1.0 (/tmp/rust_trait)
error[E0599]: no method named `walk` found for struct `Robot` in the current scope
  --> src/main.rs:10:10
   |
10 |    robot.walk();
   |          ^^^^ method not found in `Robot`
   |
  ::: src/robot/mod.rs:3:1
   |
3  | pub struct Robot {
   | ---------------- method `walk` not found for this struct
   |
  ::: src/action/mod.rs:2:7
   |
2  |    fn walk(&self);
   |       ---- the method is available for `Robot` here
   |
   = help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
   |
1  + use crate::action::Walk;
   |

For more information about this error, try `rustc --explain E0599`.
error: could not compile `rust_trait` (bin "rust_trait") due to previous error
錯誤訊息說明說雖然 Robot 有實做了 Walk Trait, 但 main 本身沒有引入 Walk trait, 因此無法使用。需要將程式改為
mod robot;
mod action;

use crate::robot::Robot;
use crate::action::Walk;

fn main() {
   let robot = Robot {
   }; 

   robot.walk();
}

這邊在 main 中引入了 Walk Trait 後,在 Robot 中實做的 walk 才能正確使用。 也就是說如果一個 struct 實做了 20 個 trait,那麼想完整使用每個 trait 的 function,需要額外再引入 20 個 trait...。

會這麼設計的理由可能是希望寫的人能夠清楚知道自己在作什麼,需要什麼東西。若編譯器沒有提示功能,使用起來感覺蠻痛苦的...。

星期日, 1月 28, 2024

千呼萬喚始出來: AMD XDNA Linux Driver

隨著 AI 的各種應用如雨後春筍般冒出,各種終端裝置也開始強調運算 AI 的能力。比如 Intel 最新發布的 Core Ultra 系列處理器加入了 NPU, AMD 最新發布的 APU 處理器皆加入了 XDNA。

相比 Intel 已經在 Linux 添加了驅動支持,AMD 腳步顯然慢得多。雖然在 github 上早早的公開了其 XDNA 的相關工具 RyzenAI-SW,但卻缺失了 Linux 相關的資源,導致在 issue tracker 上 Linux?中有大量的人在請求相關的資源。

在數天前,AMD 忽然在 github 上公佈了其 Linux Driver AMD XDNA™️ Driver for Linux®️

但目前這個 Driver 尚未整合到 mainline kernel, 也不確定是否有整合計畫,但總歸是個好的開始。

隨著這個 driver 被放出,在 llama.cpp 中的 issue [Feature request] Any plans for AMD XDNA AI Engine support on Ryzen 7x40 processors? 也開始有開發者在等待開發環境的成熟。

只是... AMD 沒有考慮直接將其整合到 ROCM 平台嗎...

星期六, 1月 20, 2024

Linux 下環境變數的儲存

今天心血來潮,忽然好奇環境變數在 Linux 的 Process 中是如何儲存的。因為在 Linux 下可以透過 /proc/$pid/environ 看到程式一開始執行時的環境變數

在 C 語言中,要存取環境變數主要可以透過

  • C functions: getenv, setenv, putenv, ...
  • 全域變數extern char **environ

透過全域變數 extern char **environ,可以知道環境變數的結構如下

在 Linux 下,如果想知道一個記憶體位置是位在哪個區塊,可以從 /proc/self/maps 觀看

在比對 environ, environ array, environ variables 後可以得知

程式開始執行時
  • environ 左值位於執行檔中(因此固定不動)
  • environ array 位於 [stack] 中
  • 所有的 environ varibales 位於 [stack] 中,且所有的 environ variables 緊密排列
當加入一個新的環境變數後或設置環境變數後
  • environ array 位於 [heap] 中
  • 所有的 environ variables 位於 [stack] 中,除了新加入的環境變數會在 [heap] 中

觀察過後會發現利用 C functions 對環境變數的修改都不會直接修改到 [stack] 中的 environ variables。於是猜測說 /proc/$pid/environ 的內容是直接對應到 [stack] 中的那些 environ varibales。於是進行實驗

environ[0][0] = 's';
sh-5.1$ cat /proc/297884/environ | tr '\0' '\n' | head -n 1
sHELL=/bin/bash
sh-5.1$

直接修改 [stack] 中的環境變數內容果然反應到了 /proc/$pid/environ 上面

在 Linux 下面想要改變 /proc/$pid/environ 對應到的 process 記憶體區塊,可以透過 prctl 搭配 PR_SET_MM_ENV_START, PR_SET_MM_ENV_END 來進行修改

以下的程式會使用 prctl 改變環境變數對應的區域,並且輸出環境變數 "SHELL" 與 /proc/$pid/environ 的內容。注意此程式需要使用 root 來跑,否則 prctl 執行會失敗

#include <sys/prctl.h>
#include <stdio.h>
#include <stdlib.h>

int
main()
{
        char buffer[4096];
        size_t nLen = 0;
        FILE *fp = NULL;
        char envs[] = "LINUX=123";

        prctl(PR_SET_MM, PR_SET_MM_ENV_START, &envs[0], 0, 0); 
        prctl(PR_SET_MM, PR_SET_MM_ENV_END, &envs[sizeof(envs)], 0, 0); 

        printf("%s\n", getenv("SHELL"));

        fp = fopen("/proc/self/environ", "r");
        nLen = fread(buffer, 1, sizeof(buffer), fp);
        fclose(fp);

        fwrite(buffer, nLen, 1, stdout);
        fputc('\n', stdout);

        return 0;
}
此程式的輸出為
/bin/bash
LINUX=123

可以看到對程式內部而言,環境變數沒有什麼變化,但從 /proc/$pid/environ 已經變成程式另外指定的區域了

星期一, 1月 01, 2024

Rust 陣列 Option 的初始化

在撰寫一些程式的過程中,需要建立一個 String 的 Array,且陣列元素有可能沒有字串。於是我寫下了以下的 Code。
fn main() {
  let s1: [Option<String>; 26] = [None; 26];
}
在 Rust 中陣列的型別使用 [TYPE; LENGTH],並且可以用 [EXPR; LENGTH] 來進行初始化。 這段程式編譯後會得到以下錯誤。
let s1: [Option<String>; 26] = 
   [None; 26];
    ^^^^ the trait `Copy` is not implemented for `String`
至於原因可以把以上的 Code 展開為
s1[0] = None;
s1[1] = None;
...
s1[25] = None;

因此會要求 [EXPR; LENGTH] 中的 EXPR 需要有 Copy Trait,否則會 Move 導致 s1[1] 以後無法被執行。而 Option<String> 中的 String 無法 Move,因此導致 Option<String> 也無法 Move。

雖然 Option::<String>::None 是不含 String,但 Some, None 都是 enum Option 中的一員,無法通過 Trait Bound 檢查可以理解。但以上的檢查有個意外的繞過方法:使用 const。

fn main() {
	const NONE: Option<String> = None;
	let s1: [Option<String>; 26] = [NONE; 26];
}

const 在 Rust 中表示其值會在編譯期間就決定,而非執行時計算。由於編譯期間就確定 [NONE; 26]中的 None 不可能是 Some,於是檢查就通過了...

在 Rust 中的官方文件有說 [EXPR; LENGTH] 中 Expr 需要符合以下條件之一
  • 有實做 Copy Trait
  • 是 const
如果以後 Rust 編譯器不用 const 關鍵字有發現 None 也是 const 的話,以後就不用寫這麼麻煩了。