Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

111 changes: 111 additions & 0 deletions browser/base/content/test/about/browser_aboutSupport.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,117 @@ add_task(async function () {
);
});

add_task(
{ skip_if: () => !AppConstants.MOZ_ENTERPRISE },
async function test_disk_encryption_row() {
// XPCOM registrations are process-local.
const CASES = [
{
status: "full",
method: "filevault",
text: "Enabled (FileVault)",
},
{
status: "full",
method: "zfs",
text: "Enabled (ZFS)",
},
{
status: "enabled",
method: "dm-crypt",
text: "Enabled (dm-crypt); inspection incomplete",
},
{
status: "partial",
method: "bitlocker",
text: "Partial (BitLocker); some mounted fixed volumes are not encrypted",
},
{
status: "disabled",
method: "dm-crypt",
text: "Disabled",
},
{
status: "in-progress",
method: "bitlocker",
text: "Encryption or decryption in progress",
},
{
// The wrapper normalizes an empty method to null.
status: "unknown",
method: "",
text: "Unknown",
},
];

await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:support" },
async browser => {
for (const testCase of CASES) {
const [l10nArgs, hidden] = await SpecialPowers.spawn(
browser,
[testCase],
async expected => {
const { MockRegistrar } = ChromeUtils.importESModule(
"resource://testing-common/MockRegistrar.sys.mjs"
);
const { Troubleshoot } = ChromeUtils.importESModule(
"resource://gre/modules/Troubleshoot.sys.mjs"
);

const cid = MockRegistrar.register(
"@mozilla.org/enterprise/disk-encryption-checker;1",
{
QueryInterface: ChromeUtils.generateQI([
Ci.nsIDiskEncryptionChecker,
]),
getDiskEncryption(callback) {
callback.onComplete(expected.status, expected.method);
},
}
);

const doc = content.document;
const id = `security-software-disk-encryption-${expected.status}`;
try {
const snapshot = await Troubleshoot.snapshot();
content.wrappedJSObject.snapshotFormatters.securitySoftware(
Cu.cloneInto(snapshot.securitySoftware, content)
);

const cell = doc.getElementById(
"security-software-disk-encryption"
);
// Wait for Fluent to replace the previous case's text.
await ContentTaskUtils.waitForCondition(
() =>
doc.l10n.getAttributes(cell).id === id &&
cell.textContent.trim() === expected.text,
`${id} rendered as "${expected.text}", got "${cell.textContent.trim()}"`
);
return [
doc.l10n.getAttributes(cell).args,
doc.getElementById("security-software-disk-encryption-row")
.hidden,
];
} finally {
MockRegistrar.unregister(cid);
}
}
);

Assert.equal(
l10nArgs.method,
testCase.method,
"The method reaches Fluent, empty when there is none"
);
Assert.ok(!hidden, "The disk encryption row is shown");
}
}
);
}
);

add_task(async function test_nimbus_experiments() {
await ExperimentAPI.ready();
let doExperimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig({
Expand Down
18 changes: 10 additions & 8 deletions browser/components/BrowserGlue.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -816,14 +816,16 @@ BrowserGlue.prototype = {
let { Troubleshoot } = ChromeUtils.importESModule(
"resource://gre/modules/Troubleshoot.sys.mjs"
);
Troubleshoot.snapshot().then(snapshotData => {
// for privacy we remove crash IDs and all preferences (but bug 1091944
// exists to expose prefs once we are confident of privacy implications)
delete snapshotData.crashes;
delete snapshotData.modifiedPreferences;
delete snapshotData.printingPreferences;
channel.send(snapshotData, target);
});
Troubleshoot.snapshot({ includeEnterpriseSecurity: false }).then(
snapshotData => {
// for privacy we remove crash IDs and all preferences (but bug 1091944
// exists to expose prefs once we are confident of privacy implications)
delete snapshotData.crashes;
delete snapshotData.modifiedPreferences;
delete snapshotData.printingPreferences;
channel.send(snapshotData, target);
}
);
}
});

Expand Down
4 changes: 4 additions & 0 deletions supply-chain/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ notes = "Upstream not yet published changes"
audit-as-crates-io = true
notes = "This is upstream plus a build fix for bug 2033279."

[policy.felt]
dependency-criteria = { plist = "safe-to-run" }
notes = "plist is used only through Value::from_reader_xml to parse stdout from the absolute, SIP-protected /usr/sbin/diskutil binary. Callers cannot supply plist syntax or structure; diskutil emits no namespaces, internal DTD subset, custom entities, or caller-controlled attributes. The binary and serde entry points are unused."

[policy.firefox-on-glean]
audit-as-crates-io = false
notes = "The crates.io version of this is just a placeholder to allow public crates to depend on firefox-on-glean."
Expand Down
29 changes: 29 additions & 0 deletions testing/enterprise/test_felt_device_posture.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,35 @@ def run_device_posture_content(self):
for edr in present_edrs:
assert "name" in edr, "Each EDR entry has a name field"

assert "diskEncryption" in device_posture, (
"Device posture reports diskEncryption"
)
disk_encryption = device_posture["diskEncryption"]
self._logger.info(f"Disk encryption: {disk_encryption}")
assert disk_encryption["status"] in (
"full",
"enabled",
"partial",
"disabled",
"in-progress",
"unknown",
), "diskEncryption reports a documented status"
expected_methods = {
"darwin": ("filevault",),
"win32": ("bitlocker",),
# A ZFS root reports native encryption rather than dm-crypt.
"linux": ("dm-crypt", "zfs"),
}.get(sys.platform, ())
if disk_encryption["status"] == "unknown":
assert disk_encryption["method"] is None, (
"An unknown status names no encryption method"
)
else:
assert disk_encryption["method"] in expected_methods, (
f"diskEncryption method should be one of {expected_methods} "
f"on {sys.platform}"
)

assert "mobileEquipmentId" in device_posture["network"], (
"Device posture reports IMEI/MEID"
)
Expand Down
34 changes: 25 additions & 9 deletions toolkit/components/enterprise/modules/DevicePosture.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
ConsoleClient: "resource://gre/modules/enterprise/ConsoleClient.sys.mjs",
createEnterpriseLogger:
"resource://gre/modules/enterprise/EnterpriseCommon.sys.mjs",
DiskEncryption: "resource://gre/modules/enterprise/DiskEncryption.sys.mjs",
EdrDetection: "resource://gre/modules/enterprise/EdrDetection.sys.mjs",
MachineId: "resource://gre/modules/enterprise/MachineId.sys.mjs",
setInterval: "resource://gre/modules/Timer.sys.mjs",
Expand Down Expand Up @@ -199,6 +200,14 @@ export const DevicePosture = {
* @property {string} name EDR agent identifier (e.g. "crowdstrike").
*/

/**
* @typedef {object} DeviceDiskEncryption
* @property {"full"|"enabled"|"partial"|"disabled"|"in-progress"|"unknown"} status
* Aggregated encryption status.
* @property {"filevault"|"bitlocker"|"dm-crypt"|"zfs"|null} method
* Platform mechanism checked, or null for an unknown status.
*/

/**
* @typedef {object} DevicePosture
* @property {object} os Telemetry-reported os information.
Expand All @@ -210,6 +219,8 @@ export const DevicePosture = {
* @property {boolean} secureBootEnabled Whether Secure Boot is enabled.
* @property {boolean} isDomainJoined Whether the machine is joined to a domain (Windows on-prem AD or Azure AD/Entra).
* @property {DeviceEdr[]} presentEdrs Detected EDR agents (empty if none, or if the console asked us to probe none).
* @property {DeviceDiskEncryption} diskEncryption Disk encryption for the
* boot and other mounted fixed volumes.
*/

/**
Expand Down Expand Up @@ -290,15 +301,19 @@ export const DevicePosture = {
);
};

// These probes are independent, and some are slow (subprocess spawns, an
// `ioreg` shell-out), so run them concurrently.
const [mobileEquipmentId, extensions, machineId, presentEdrs] =
await Promise.all([
getImeiValue(),
this.getExtensions({ profileDir }),
getMachineId(),
getPresentEDRs(),
]);
const [
mobileEquipmentId,
extensions,
machineId,
presentEdrs,
diskEncryption,
] = await Promise.all([
getImeiValue(),
this.getExtensions({ profileDir }),
getMachineId(),
getPresentEDRs(),
lazy.DiskEncryption.getStatus(),
]);

const devicePosturePayload = {
os,
Expand All @@ -314,6 +329,7 @@ export const DevicePosture = {
Services.sysinfo.getPropertyAsBool("secureBootEnabled"),
isDomainJoined: Services.sysinfo.getPropertyAsBool("isDomainJoined"),
presentEdrs,
diskEncryption,
};
return devicePosturePayload;
},
Expand Down
8 changes: 7 additions & 1 deletion toolkit/components/felt/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ time = "0.3.36"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[dev-dependencies]
tempfile = "3"

[target.'cfg(unix)'.dependencies]
libc = "0.2"

[target.'cfg(target_os = "macos")'.dependencies]
plist = "1"

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["tlhelp32", "handleapi", "processthreadsapi", "winbase", "winnt", "winsvc", "errhandlingapi", "winerror"] }
winapi = { version = "0.3", features = ["tlhelp32", "handleapi", "processthreadsapi", "winbase", "winnt", "winsvc", "errhandlingapi", "winerror", "fileapi", "sysinfoapi"] }
56 changes: 56 additions & 0 deletions toolkit/components/felt/rust/FeltDiskEncryptionWin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <windows.h>

#include <propidl.h>
#include <propsys.h>
#include <shlobj.h>

#include "mozilla/Assertions.h"
#include "mozilla/RefPtr.h"
#include "mozilla/mscom/Utils.h"
#include "nsCOMPtr.h"

// Explorer's BitLocker property. Older SDKs omit it from propkey.h.
static const PROPERTYKEY kVolumeBitLockerProtection = {
{0x2d15a9a1,
0xa556,
0x4189,
{0x91, 0xad, 0x02, 0x74, 0x58, 0xf1, 0x1a, 0x07}},
1717};

/**
* Reads System.Volume.BitLockerProtection for a mount path such as "C:\".
* Returns false if the property is missing or is not an integer.
*
* Runs synchronously on the caller's background thread. That thread never
* initializes COM itself; it belongs to the implicit MTA because
* mscom::ProcessRuntime keeps the process MTA alive from startup.
*/
extern "C" bool felt_read_bitlocker_protection(const char16_t* aRoot,
int32_t* aOutValue) {
MOZ_ASSERT(mozilla::mscom::IsCurrentThreadMTA());
if (!mozilla::mscom::IsCurrentThreadMTA()) {
return false;
}

RefPtr<IPropertyStore> store;
HRESULT hr = SHGetPropertyStoreFromParsingName(
reinterpret_cast<const wchar_t*>(aRoot), nullptr, GPS_DEFAULT,
IID_IPropertyStore, getter_AddRefs(store));
if (FAILED(hr) || !store) {
return false;
}

PROPVARIANT value;
PropVariantInit(&value);
hr = store->GetValue(kVolumeBitLockerProtection, &value);
bool read = SUCCEEDED(hr) && (value.vt == VT_I4 || value.vt == VT_UI4);
if (read) {
*aOutValue = value.lVal;
}
PropVariantClear(&value);
return read;
}
8 changes: 8 additions & 0 deletions toolkit/components/felt/rust/components.conf
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,12 @@ Classes = [
'singleton': True,
'interfaces': ['nsIEdrChecker'],
},
{
'cid': '{9545d213-062a-4958-82f9-6022f0d3f357}',
'contract_ids': ['@mozilla.org/enterprise/disk-encryption-checker;1'],
'headers': ['mozilla/toolkit/components/felt/felt.h'],
'legacy_constructor': 'disk_encryption_checker_constructor',
'singleton': True,
'interfaces': ['nsIDiskEncryptionChecker'],
},
]
10 changes: 7 additions & 3 deletions toolkit/components/felt/rust/felt.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ void felt_init();
bool is_felt_ui();

#ifdef MOZ_WIDGET_GTK
void felt_set_startup_token_or_timestamp(const char* aToken, uint32_t aTimestamp);
void felt_get_startup_token_or_timestamp(const char** aOutToken, uint32_t* aOutTokenLen,
uint32_t* aOutTimestamp);
void felt_set_startup_token_or_timestamp(const char* aToken,
uint32_t aTimestamp);
void felt_get_startup_token_or_timestamp(const char** aOutToken,
uint32_t* aOutTokenLen,
uint32_t* aOutTimestamp);
#endif

#ifdef XP_MACOSX
Expand All @@ -37,6 +39,8 @@ nsresult felt_restartforced_constructor(REFNSIID iid, void** result);

nsresult edr_checker_constructor(REFNSIID iid, void** result);

nsresult disk_encryption_checker_constructor(REFNSIID iid, void** result);

} // extern "C"

#endif // felt_h
5 changes: 5 additions & 0 deletions toolkit/components/felt/rust/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
LOCAL_INCLUDES += [
"/widget/gtk",
]
elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
SOURCES += [
"FeltDiskEncryptionWin.cpp",
]

FINAL_LIBRARY = "xul"

XPIDL_SOURCES += [
"nsIDiskEncryptionChecker.idl",
"nsIEdrChecker.idl",
"nsIFelt.idl",
"nsIFeltRestartForced.idl",
Expand Down
Loading