共用方式為


啟動 AppContainer

本文說明啟動 AppContainer 或許可權較低的 AppContainer 所需的步驟,包括相關的程式碼範例。 AppContainers 是 Windows 8 中引入的一項安全功能,用於增強應用程式進程的隔離和控制。 它們為應用程式提供了一個沙盒環境,限制它們存取系統或彼此資源以及使用者資料的能力,除非明確允許。 AppContainers 會利用現有的 Windows 安全性機制,例如安全性識別碼 (SID) 、權杖和安全性描述元隨意存取控制清單 (DACL) 來強制執行這些限制。

術語

下表定義了本文中參考的術語和概念。

術語 Description
套件身分識別 套件身分識別是一種邏輯建構,可唯一識別套件。 如需詳細資訊,請參閱 Windows 應用程式中的套件身分識別概觀
安全性識別碼 (SID) SID 可用來唯一識別安全性主體或安全組。 安全性主體可以代表作系統可以驗證的任何實體。 範例包括用戶帳戶、計算機帳戶,或執行於使用者或計算機帳戶安全性內容中的線程或進程。 如需詳細資訊,請參閱 安全性識別碼
功能 SID 功能 SID 可作為功能的唯一和不可變標識碼。 功能代表不可偽造的授權權記號,可授與應用程式存取資源 (例如,文件、相機和位置) 。 如需詳細資訊,請參閱 應用程式功能宣告
自由存取控制清單 (DACL) 識別可對物件執行各種作業的使用者和群組的清單。 如需詳細資訊,請參閱 安全性描述元元件
較低許可權的 AppContainers (LPAC) 一種比一般 AppContainer 更隔離的 AppContainer 類型。 它需要明確的功能宣告,才能存取 AppContainers 可存取的資源。

AppContainer 概觀

當應用程式以 AppContainer 身分執行時,其存取權杖會包含唯一的應用程式套件身分識別 (套件 SID) 和一或多個功能 SID。 針對 AppContainers,功能可用來確保 AppContainers 可以以盡可能低的許可權執行,而且只有在需要時才被授予對潛在敏感性資源的存取權。 例如,如果沒有 網路 功能,AppContainer 就無法存取網路,如果沒有 網路攝影機 功能,就無法存取攝影機。 AppContainer SID (套件和功能 SID) 與傳統的使用者和群組 SID 不同,權杖的兩個部分都需要透過物件的任意存取控制清單 (DACL) 授與受保護資源的存取權。 這種雙主體模型確保對敏感資源的存取受到嚴格控制,並且可以針對不同的應用程式進行獨立管理。 它也可確保必須明確授與 AppContainers 對指定資源的存取權。 此外,允許的存取權是使用者/群組 SID 和 AppContainer SID 所授與的存取權的交集,因此如果使用者具有完整存取權,但 AppContainer 只有讀取存取權,則只能授與 AppContainer 讀取存取權。 同樣地,如果使用者具有讀取和執行存取權,但 AppContainer 具有完整存取權,則只能授與 AppContainer 讀取和執行存取權。

AppContainer 以低完整性層級 (IL) 執行,這進一步限制了它們與系統上較高完整性物件互動的能力。 不過,如果資源的必要標籤為 [中 IL] 或更低,且 DACL 會授與 AppContainer 的存取權 (透過套件 SID 或功能 SID) ,則 AppContainer 可以讀取、寫入或執行,視授與 DACL 中兩個主體的存取權而定。 這種方法提供了靈活性和安全性,允許存取必要的資源,同時限制不受信任或受損的應用程式的潛在有害操作。

AppContainer 與存取屬於其他應用程式的進程和視窗,以及裝置、檔案/目錄、登錄機碼、網路和認證隔離。 因此,它們提供了一種沙箱機制來對潛在的風險操作進行沙箱處理,例如安全地解析不受信任的資料。

一般 AppContainer 會授與特定系統檔案/目錄、一般登錄機碼和 COM 物件的存取權,不過,LPAC 需要特定功能來存取一般 AppContainers 可以存取的資源。 較低許可權的 AppContainer (LPAC) 比一般 AppContainer 更隔離,而且需要進一步的功能才能存取一般 AppContainer 已可存取的資源,例如登錄、檔案等。 例如,除非 LPAC 具有 registryRead 功能,否則無法開啟登錄中的任何金鑰,除非它具有 lpacCom 功能,否則無法使用 COM。

啟動 AppContainer

如先前所述,AppContainers 具有唯一的套件 SID,可確保自己的資源受到保護,免受其他應用程式的影響。 套件 SID 衍生自指定 AppContainer 的字串名稱 (Moniker) 。 如果是透過 AppX 資訊清單產生的 AppContainer,這是套件系列名稱 (PFN) ,但在應用程式啟動 AppContainer 程式本身的情況下,應用程式必須判斷它想要提供 AppContainer 的名稱 (名字)。

啟動 AppContainer 涉及數個步驟。 需要決定要授與的必要功能,必須為 AppContainer 建立配置檔,以便 AppContainer 可以建立/讀取/寫入檔案的位置,需要包含進程和執行緒屬性,以通知 Windows 它需要建立 AppContainer。

建構功能

AppContainer (或 LPAC) 可能需要存取不同資源的功能,例如網路、位置,或在 LPAC 的情況下,甚至是登錄或 COM 物件。 建構功能可以透過 DeriveCapabilitySidsFromName API 來達成,不過最好將此 API 包裝在協助程式函式中,例如下列範例程式碼中所示的 GetCapabilitySidFromName ,因為群組 SID 僅用於服務,而且 API 只會傳回單一功能。

BOOL GetCapabilitySidFromName( 
    PCWSTR CapabilityName, 
    PSID* CapabilitySid) 
{ 
    PSID* CapabilitySids; 
    DWORD CapabilitySidCount; 
    PSID* GroupSids; 
    DWORD GroupSidCount; 

    *CapabilitySid = NULL; 

    if (DeriveCapabilitySidsFromName(CapabilityName, &GroupSids, &GroupSidCount, & CapabilitySids, &CapabilitySidCount)) 
    { 
        LocalFree(GroupSids[0]); 
        LocalFree(GroupSids); 

        *CapabilitySid = CapabilitySids[0]; 
        LocalFree(CapabilitySids); 
        return TRUE; 

    }

    return FALSE; 
} 

下列範例程式碼示範使用上一個範例中定義的 GetCapabilitySidFromName 協助程式函式來建構 internetClient位置 功能。

BOOL BuildAppContainerCapabilities( 
    PSID_AND_ATTRIBUTES* Capabilities, 
    DWORD* NumberOfCapabilities 
) 
{
    DWORD CapabilityCount; 
    PSID CapabilitySids[2]; 
    PSID_AND_ATTRIBUTES LocalCapabilities; 

    *Capabilities = NULL; 
    *NumberOfCapabilities = 0; 

    CapabilityCount = 0; 

    if (GetCapabilitySidFromName(L"internetClient", &CapabilitySids[CapabilityCount++]) == FALSE) 
    { 
        return FALSE; 
    } 

    if (GetCapabilitySidFromName(L"location", &CapabilitySids[CapabilityCount++]) == FALSE) 
    { 
        for (DWORD i = 0; i < CapabilityCount; ++i) 
        { 
            LocalFree(CapabilitySids[i]); 
        } 

        return FALSE; 
    } 

    LocalCapabilities =  
        (PSID_AND_ATTRIBUTES)HeapAlloc(GetProcessHeap(), 
        0, 
        CapabilityCount * sizeof(SID_AND_ATTRIBUTES)); 

    if (LocalCapabilities != NULL) 
    { 
        for (DWORD i = 0; i < CapabilityCount; ++i) 
        { 
            LocalCapabilities[i].Sid = CapabilitySids[i]; 
            LocalCapabilities[i].Attributes = SE_GROUP_ENABLED; 
        } 
    }
    else 
    { 
        for (DWORD i = 0; i < CapabilityCount; ++i) 
        {
            LocalFree(CapabilitySids[i]); 
        } 

        return FALSE; 
    } 

    *Capabilities = LocalCapabilities; 
    *NumberOfCapabilities = CapabilityCount; 

    return TRUE; 
} 

建立設定檔

呼叫 CreateAppContainerProfile 來建立 AppContainer,並可透過 LOCALAPPDATA 環境變數或呼叫 GetAppContainerFolderPath 來存取設定檔。 針對新的 AppContainer,這也會傳回 AppContainer 的套件 SID。 不過,針對現有的 AppContainer,必須使用 DeriveAppContainerSidFromAppContainerName API 從名字對象衍生套件 SID。 TMPTEMP 環境變數也會重新路由傳送至設定檔位置下 AppContainer 可存取的目錄:

LOCALAPPDATA=C:\Users\TestUser\AppData\Local\Packages\TestAppContainer\AC

TEMP=C:\Users\TestUser\AppData\Local\Packages\TestAppContainer\AC\Temp

TMP=C:\Users\TestUser\AppData\Local\Packages\TestAppContainer\AC\Temp

下列範例程式碼示範如何使用 CreateAppContainerProfile 函式來建立 AppContainer 設定檔,並擷取新或現有 AppContainer 的 SID。

HRESULT
CreateProfileForAppContainer(
    PCWSTR AppContainerName,
    PSID_AND_ATTRIBUTES Capabilities,
    ULONG NumberOfCapabilities,
    PCWSTR DisplayName,
    PCWSTR Description,
    PSID* AppContainerSid
    )
{
    HRESULT hr;
    PSID LocalAppContainerSid = NULL;

    *AppContainerSid = NULL;

    hr = CreateAppContainerProfile(AppContainerName,
                                   DisplayName,
                                   Description,
                                   Capabilities,
                                   NumberOfCapabilities,
                                   &LocalAppContainerSid);

    if (FAILED(hr)) {
        if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) {

            //
            // Obtain the AppContainer SID based on the AppContainer name.
            //

            hr = AppContainerDeriveSidFromMoniker(AppContainerName,
                                                  &LocalAppContainerSid);
            
            if (FAILED(hr)) {   
                return hr;
            }

        } else {
            return hr;
        }        
    } 

    //
    // Since this is successful, set the output AppContainer SID accordingly.
    //
    
    *AppContainerSid = LocalAppContainerSid;

    return S_OK;
}

啟動 AppContainer (或 LPAC)

若要啟動 AppContainer 或 LPAC 處理程序,必須在啟動資訊結構 STARTUPINFOEX 中包含特定欄位。 具體來說, lpAttributeList 欄位是必要的,因為這允許指示 CreateProcess 建立 AppContainer 環境的其他資訊,其中包含物件命名空間和權杖。 lpAttributeList 欄位的類型為 LPPROC_THREAD_ATTRIBUTE_LIST,設定方式如下。

下列範例示範如何啟動一般應用程式容器。

STARTUPINFOEX si = {0}; 
LPPROC_THREAD_ATTRIBUTE_LIST AttributeList = NULL; 
SECURITY_CAPABILITIES SecurityCapabilities; 
DWORD AttributeCount = 1; 
SIZE_T AttributesLength = 0; 

if (!InitializeProcThreadAttributeList(NULL, AttributeCount, 0, &AttributesLength)) 
{
    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)  
    { 
        return GetLastError(); 
    }
} 

AttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 
    0, 
    AttributesLength); 

if (AttributeList == NULL) 
{ 
    return ERROR_OUTOFMEMORY; 
} 

if (!InitializeProcThreadAttributeList(AttributeList, AttributeCount, 0, &AttributesLength)) 
{ 
    if (GetLastError() != ERROR_SUCCESS) 
    {
        return GetLastError(); 
    }
} 

SecurityCapabilities.CapabilityCount = NumberOfCapabilities; 
SecurityCapabilities.Capabilities = Capabilities; 
SecurityCapabilities.AppContainerSid = PackageSid; 
SecurityCapabilities.Reserved = 0; 

if (!UpdateProcThreadAttribute(AttributeList, 
        0,
        PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, 
        &SecurityCapabilities, 
        sizeof(SecurityCapabilities), 
        NULL, 
        NULL 
        )) 
{ 
    return GetLastError(); 
} 

si.StartupInfo.cb = sizeof(si); 
si.lpAttributeList = AttributeList; 

下列範例示範如何啟動許可權較低的 AppContainer (LPAC),這需要額外的進程/執行緒屬性:

STARTUPINFOEX si = {0}; 
LPPROC_THREAD_ATTRIBUTE_LIST AttributeList = NULL; 
SECURITY_CAPABILITIES SecurityCapabilities; 
DWORD AttributeCount = 2; 
SIZE_T AttributesLength = 0; 
DWORD AllApplicationPackagesPolicy; 

if (!InitializeProcThreadAttributeList(NULL, AttributeCount, 0, &AttributesLength)) 
{ 
    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) 
    { 
        return GetLastError(); 
    } 
} 

AttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 
    0, 
    AttributesLength); 

if (AttributeList == NULL) 
{ 
    return ERROR_OUTOFMEMORY; 
} 

if (!InitializeProcThreadAttributeList(AttributeList, AttributeCount, 0, &AttributesLength)) 
{ 
    if (GetLastError() != ERROR_SUCCESS) 
    { 
        return GetLastError(); 
    } 
} 

SecurityCapabilities.CapabilityCount = NumberOfCapabilities; 
SecurityCapabilities.Capabilities = Capabilities; 
SecurityCapabilities.AppContainerSid = PackageSid; 
SecurityCapabilities.Reserved = 0; 

if (!UpdateProcThreadAttribute(AttributeList, 
    0, 
    PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, 
    &SecurityCapabilities, 
    sizeof(SecurityCapabilities), 
    NULL, 
    NULL 
    )) 
{
    return GetLastError(); 
} 

AllApplicationPackagesPolicy = PROCESS_CREATION_ALL_APPLICATION_PACKAGES_OPT_OUT; 

if (!UpdateProcThreadAttribute(AttributeList, 
    0, 
    PROC_THREAD_ATTRIBUTE_ALL_APPLICATION_PACKAGES_POLICY, 
    &AllApplicationPackagesPolicy, 
    sizeof(AllApplicationPackagesPolicy), 
    NULL, 
    NULL 
    )) 
{ 
    return GetLastError(); 
} 

si.StartupInfo.cb = sizeof(si); 
si.lpAttributeList = AttributeList; 

建立 AppContainer/LPAC 程序

最後一步是使用啟動資訊啟動進程,其中包括先前步驟中建構的進程/執行緒屬性。 這會包括套件 SID、功能 (如果有的話),以及這是否應該是 LPAC,這是透過選擇退出所有應用程式套件來指定。

if (!CreateProcess(NULL, 
    <path to executable>, 
    NULL, 
    NULL, 
    FALSE, 
    EXTENDED_STARTUPINFO_PRESENT, 
    NULL, 
    NULL, 
    (LPSTARTUPINFOW)&si, 
    &pi)) 
{ 
    return GetLastError(); 
}

安全性識別碼

安全性描述元元件