目錄

名稱

安全 - 編譯並執行受限區塊中的程式碼

語法

use Safe;

$compartment = new Safe;

$compartment->permit(qw(time sort :browse));

$result = $compartment->reval($unsafe_code);

說明

Safe 擴充模組允許建立區塊,可在其中評估 Perl 程式碼。每個區塊都有

新的命名空間

命名空間的「根」(例如「main::」) 會變更為不同的套件,且在區塊中評估的程式碼無法參考此命名空間以外的變數,即使使用執行時期的 glob 查詢和其他技巧。

在隔離區外編譯的程式碼可以選擇將變數放入(或與)隔離區的命名空間共用變數,且只有在隔離區評估的程式碼才能看到該資料。

預設情況下,與隔離區共用的唯一變數是「底線」變數 $_ 和 @_(以及技術上較少使用的 %_、_ 檔案句柄等)。這是因為否則預設為 $_ 的 Perl 運算子將無法運作,而且在子常式進入時也無法將參數指定給 @_。

運算子遮罩

每個隔離區都有關聯的「運算子遮罩」。請回想一下,Perl 程式碼在執行前會編譯成內部格式。評估 Perl 程式碼(例如透過「eval」或「do 'file'」)會導致程式碼編譯成內部格式,然後在編譯過程中沒有錯誤的情況下執行。在隔離區評估的程式碼會根據隔離區的運算子遮罩進行編譯。嘗試在包含遮罩運算子的隔離區中評估程式碼會導致編譯失敗並出現錯誤。程式碼將不會執行。

新建立隔離區的預設運算子遮罩為「:default」optag。

請務必閱讀 Opcode 模組文件以取得更多資訊,特別是 opnames、optags 和 opsets 的詳細定義。

由於只有在編譯階段才會套用運算子遮罩,因此可以透過將包覆子常式的控制代碼(在隔離區外撰寫)放入隔離區來控制對潛在不安全操作的存取。例如,

$cpt = new Safe;
sub wrapper {
  # vet arguments and perform potentially unsafe operations
}
$cpt->share('&wrapper');

警告

Safe 模組並未實作一個有效的沙盒來評估 Perl 解譯器中不受信任的程式碼。

可以濫用來繞過 Safe 限制的 Perl 解譯器中的錯誤不會被視為漏洞。請參閱 perlsecpolicy 以取得更多資訊。

作者對此軟體在安全目的上的適用性不作任何明示或暗示的保證

在任何情況下,作者都不對因使用此軟體而產生的特殊、附帶、後果性、間接或其他類似損害負責。

您的里程數會有所不同。如有任何疑慮,請勿使用

方法

若要建立新的隔離區,請使用

$cpt = new Safe;

選用參數為 (NAMESPACE),其中 NAMESPACE 是要為隔離區使用的根命名空間(預設為「Safe::Root0」,每個新隔離區都會遞增)。

請注意,Safe 模組的 1.00 版支援第二個選用參數 MASK。該功能已暫停,等待更深入的考量。請使用下方說明的許可和拒絕方法。

然後,可以在上面建構函式傳回的區隔物件上使用下列方法。每個案例中的物件引數都是隱含的。

permit (OP, ...)

允許在區隔中編譯程式碼時使用列出的運算子(除了已經允許的運算子之外)。

你可以依名稱列出操作碼,或使用標籤名稱;請參閱"操作碼中的預定義操作碼標籤"

permit_only (OP, ...)

允許在區隔中編譯程式碼時使用列出的運算子(允許其他運算子)。

deny (OP, ...)

禁止在區隔中編譯程式碼時使用列出的運算子(仍可能允許其他運算子)。

deny_only (OP, ...)

禁止在區隔中編譯程式碼時使用列出的運算子(所有其他運算子都將被允許,所以你可能不想使用這個方法)。

trap (OP, ...), untrap (OP, ...)

trap 和 untrap 方法分別是 deny 和 permit 的同義詞。

share (NAME, ...)

這會將引數清單中的變數與區隔分享。這幾乎與使用Exporter模組匯出變數相同。

每個 NAME 都必須是非詞彙變數的名稱,通常包含開頭的類型識別碼。裸字會被視為函式名稱。

合法名稱的範例包括:$foo(標量)、@foo(陣列)、%foo(雜湊)、&foo 或 foo(子常式),以及 *foo(glob)(即與「foo」相關的所有符號表項目,包括標量、陣列、雜湊、子常式和檔案處理器)。

每個 NAME 都假設在呼叫套件中。請參閱 share_from 以取得替代方法(share 使用這個方法)。

share_from (PACKAGE, ARRAYREF)

這個方法類似於 share(),但允許你明確命名要從中分享符號的套件。符號名稱(包括類型字元)會提供為陣列參考。

$safe->share_from('main', [ '$foo', '%bar', 'func' ]);

名稱可以包含套件名稱,這些名稱相對於指定的 PACKAGE。因此這兩個呼叫具有相同的效果

$safe->share_from('Scalar::Util', [ 'reftype' ]);
$safe->share_from('main', [ 'Scalar::Util::reftype' ]);

varglob (VARNAME)

這會傳回隔間中 VARNAME 符號表條目的 glob 參考。VARNAME 必須是變數的名稱,且不帶任何前置類型標記。例如

${$cpt->varglob('foo')} = "Hello world";

$cpt = new Safe 'Root';
$Root::foo = "Hello world";

具有相同的效果,但不需要知道 $cpt 的套件名稱。

reval (STRING, STRICT)

這會在隔間內將 STRING 評估為 perl 程式碼。

程式碼只能看到隔間的命名空間(由root 方法傳回)。隔間的根套件對隔間內的程式碼來說看起來像是 main:: 套件。

如果 STRING 中的程式碼嘗試使用隔間不允許的運算子,就會造成錯誤(在主程式執行期間,但對於 STRING 中的程式碼來說是在編譯期間)。錯誤的格式為「'%s' 遭到運算遮罩攔截...」。

如果運算以這種方式遭到攔截,則 STRING 中的程式碼將不會執行。如果發生這種攔截運算或任何其他編譯期間或傳回錯誤,則 $@ 會設定為錯誤訊息,就像 eval() 一樣。

如果沒有錯誤,則此方法會傳回最後評估的運算式值,或可以使用傳回陳述式,就像子常式和eval() 一樣。呼叫者會像往常一樣決定內容(清單或純量)。

如果 reval() 的傳回值是(或包含)任何程式碼參考,則這些程式碼參考會被包裝,以便始終在隔間內執行。請參閱 "wrap_code_refs_within"

以前未記錄的 STRICT 參數會設定嚴格性:如果為 true,則會使用 'use strict;',否則會使用 'no strict;'。注意:如果省略 STRICT,則 'no strict;' 為預設值。

一些注意事項

如果允許 entereval op,則程式碼可以使用 eval "..." 來「隱藏」可能使用拒絕 op 的程式碼。這不是一個主要問題,因為當程式碼嘗試執行 eval 時,它會失敗,因為 opmask 仍然有效。然而,這種技術會允許聰明且可能有害的程式碼「探測」可能的界線。

在隔間中執行的程式碼所執行的任何字串 eval,或由在隔間中執行的程式碼呼叫的程式碼所執行的 eval,都將在隔間的命名空間中進行 eval。這是一個潛在的嚴重問題。

考慮一個在隔離區外編譯但與其共用的封裝 pkg 中的函式 foo()。假設隔離區有一個稱為「Root」的根封裝。如果 foo() 包含 eval 陳述式,例如 eval '$foo = 1',則通常會將 $pkg::foo 設定為 1。如果 foo() 從隔離區呼叫(透過任何方式),則 eval 實際上會設定 $Root::pkg::foo,而不是設定 $pkg::foo。

這可以用模組(例如使用 eval "..." 作為 AUTOLOAD 函式一部分的 Socket 模組)輕鬆示範。您可以在隔離區外「use」模組,並與隔離區共用(自動載入)函式。如果自動載入是由隔離區中的程式碼觸發,或由任何方式從隔離區呼叫的任何程式碼觸發,則 Socket 模組的 AUTOLOAD 函式中的 eval 會在隔離區的命名空間中發生。由 eval 的程式碼建立或使用的任何變數現在都受到隔離區中程式碼的控制。

類似的效應適用於從隔離區呼叫但未在其中編譯的程式碼中所有執行時期符號查詢。

rdo (FILENAME)

這會評估隔離區內 FILENAME 檔案的內容。它使用與 Perl 內建的 do 相同的規則來尋找檔案,可能會使用 @INC

請參閱reval 方法的上述文件,以取得更多詳細資訊。

root (NAMESPACE)

此方法會傳回隔離區命名空間根目錄的封裝名稱。

請注意,此行為與 Safe 模組的 1.00 版不同,在該版本中,根目錄模組可用於變更命名空間。該功能已暫停,等待更深入的考量。

mask (MASK)

這是隔離區運算子遮罩的取得或設定方法。

如果沒有 MASK 參數,它會傳回隔離區目前的運算子遮罩。

如果存在 MASK 參數,它會設定隔離區的運算子遮罩(等同於呼叫 deny_only 方法)。

wrap_code_ref (CODEREF)

傳回匿名子程式的參考,當執行時,它會在 Safe 隔離區「生效」時呼叫 CODEREF。換句話說,調整封裝命名空間並啟用 opmask。

請注意,opmask 不會影響已編譯的程式碼,它只會影響已編譯的程式碼可能嘗試執行的任何進一步編譯。

這在套用於從 reval() 傳回的程式碼參考時特別有用。

(它也提供了一種 RT#60374 的解決方法:「Safe.pm sort {} 錯誤,-Dusethreads」。請參閱 https://rt.perl.org/rt3//Public/Bug/Display.html?id=60374 以取得更多詳細資訊。)

wrap_code_refs_within (...)

封裝參數中找到的任何 CODE 參考,方法是將每個參考替換為在 CODE 參考上呼叫 "wrap_code_ref" 的結果。參數中的任何 ARRAY 或 HASH 參考都會遞迴檢查。

不傳回任何內容。

風險

此區段僅概述區隔中程式碼可能(有意或無意)執行的某些動作,這些動作會對區隔外產生影響。

記憶體

耗盡所有(或幾乎所有)可用記憶體。

CPU

導致無限迴圈等問題。

窺探

從您的系統中複製私人資訊。即使像您的使用者名稱這麼簡單的資訊對其他人來說也很有價值。例如,可以從您的環境變數中收集到許多有用的資訊。

訊號

導致訊號(特別是 SIGFPE 和 SIGALARM)影響您的程序。

設定訊號處理常式時需要仔細考慮並控制。當呼叫訊號處理常式時,哪個遮罩會生效?如果使用者可以讓輸入函式取得例外並呼叫使用者的訊號處理常式,在呼叫處理常式之前是否會重新套用使用者的受限遮罩?輸入處理常式會使用其原始遮罩還是使用者的遮罩來呼叫?

狀態變更

很明顯地,例如 chdir 等操作會影響整個程序,而不會只影響區隔中的程式碼。例如 rand 和 srand 等操作有類似但較為細微的影響。

作者

最初由 Malcolm Beattie 設計和實作。

Tim Bunce 重新設計以使用 Opcode 模組,並新增其他變更。

目前由 Perl 5 Porters (perl5-porters@perl.org) 維護。