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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "MacKernelSDK"]
path = MacKernelSDK
url = https://github.com/acidanthera/MacKernelSDK.git
166 changes: 166 additions & 0 deletions Code-Signing-Guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Code Signing Guide for Innie Kernel Extension

This guide shows how to sign the Innie kernel extension with your Apple Developer account to avoid disabling SIP.

## Prerequisites

1. **Apple Developer Account** (Individual or Organization - Enterprise not required)
2. **Xcode** installed with Command Line Tools
3. **Valid Developer ID Application certificate**

## Step 1: Obtain Developer Certificates

### Method A: Through Xcode (Recommended)
1. Open **Xcode**
2. Go to **Xcode β†’ Preferences β†’ Accounts** (or **Xcode β†’ Settings β†’ Accounts** in newer versions)
3. Click the **"+"** button and **Add Apple ID**
4. Enter your Apple ID credentials that have the Developer account
5. After signing in, select your **Team** from the list
6. Click **"Manage Certificates..."**
7. Click the **"+"** button and select **"Developer ID Application"**
8. This creates and downloads the certificate you need for kernel extension signing

### Method B: Through Apple Developer Portal (Alternative)
1. Log into [developer.apple.com](https://developer.apple.com)
2. Go to **Certificates, Identifiers & Profiles**
3. Click **Certificates** β†’ **"+"** button
4. Select **"Developer ID Application"** (under Production section)
5. Follow the prompts to create a Certificate Signing Request (CSR)
6. Upload the CSR and download the certificate
7. Double-click the downloaded certificate to install it

### Troubleshooting Certificate Setup
If you see "0 valid identities found" after setup:
- Restart Xcode and check Preferences β†’ Accounts again
- Ensure you're signed into the correct Apple ID with Developer program
- Try logging out and back into your Apple ID in Xcode
- Check Keychain Access app - certificates should appear in "login" keychain

## Step 2: Verify Certificates

Check available signing identities:
```bash
security find-identity -v -p codesigning
```

You should see something like:
```
1) ABCDEF1234567890 "Developer ID Application: Your Name (TEAMID123)"
```

## Step 3: Sign the Kernel Extension

### Automatic Signing (Update Xcode Project)

Edit the Innie Xcode project to enable automatic signing:

1. Open `Innie.xcodeproj` in Xcode
2. Select the **Innie** target
3. In **Signing & Capabilities**:
- Check **Automatically manage signing**
- Select your **Team**
- **Bundle Identifier**: `com.yourname.kext.Innie`
4. Build the project - it will be signed automatically

### Manual Signing (Command Line)

If you prefer command line signing:

```bash
# Sign the kernel extension
codesign --force --sign "Developer ID Application: Your Name (TEAMID)" \
--entitlements Innie.entitlements \
build/Release/Innie.kext

# Verify signature
codesign -vvv --deep --strict build/Release/Innie.kext
spctl -a -t exec -vv build/Release/Innie.kext
```

## Step 4: Create Entitlements File

Kernel extensions need specific entitlements:

**File: `Innie.entitlements`**
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.driverkit.allow-any-userclient-access</key>
<true/>
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
</dict>
</plist>
```

## Step 5: Notarization (Optional but Recommended)

For distribution, you should notarize the signed kernel extension:

```bash
# Create a ZIP for notarization
zip -r Innie-signed.zip build/Release/Innie.kext

# Submit for notarization
xcrun notarytool submit Innie-signed.zip \
--apple-id [email protected] \
--team-id YOUR_TEAM_ID \
--password "your-app-specific-password" \
--wait

# Staple the notarization
xcrun stapler staple build/Release/Innie.kext
```

## Benefits of Code Signing

βœ… **No SIP modification required** - kernel extension loads with SIP enabled
βœ… **Better security** - maintains system integrity protection
βœ… **Proper distribution** - signed kexts are trusted by macOS
βœ… **Future compatibility** - Apple increasingly requires signed kernel extensions

## Installation with Signed Kext

With a properly signed kernel extension:
1. SIP can remain **enabled** (no Recovery Mode needed)
2. Standard installation process works
3. No security warnings or blocks
4. Professional distribution capability

## Cost Considerations

- **Individual Developer Account**: $99/year - sufficient for personal use
- **Organization Account**: $99/year - same capabilities for kernel extensions
- **Enterprise Account**: $299/year - NOT required for kernel extension signing

## Verification Commands

After signing, verify the signature:
```bash
# Check signature validity
codesign -vvv --deep --strict Innie.kext

# Check system acceptance
spctl -a -t exec -vv Innie.kext

# Verify it will load
kextutil -n -t Innie.kext
```

## Troubleshooting

**Certificate Issues:**
- Ensure certificates are installed in System keychain
- Check certificate expiration dates
- Verify Team ID matches in certificates and code

**Signing Failures:**
- Use `--deep` flag for embedded frameworks
- Include proper entitlements file
- Check for hardened runtime conflicts

**Loading Issues:**
- Verify signature after installation: `codesign -v /Library/Extensions/Innie.kext`
- Check system logs for signature validation errors
10 changes: 10 additions & 0 deletions Innie.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.driverkit.allow-any-userclient-access</key>
<true/>
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
</dict>
</plist>
160 changes: 104 additions & 56 deletions Innie/Innie.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,25 @@ void Innie::recurseBridge(IORegistryEntry *entry) {
if (auto class_code = childEntry->getProperty("class-code")) {
if (auto codeData = OSDynamicCast(OSData, class_code)) {
code=*(uint32_t*)codeData->getBytesNoCopy();
if (code == classCode::SATADevice || code == classCode::NVMeDevice){
DBGLOG("found device %s", childEntry->getName());
if (auto built_in = childEntry->getProperty("built-in")) {
DBGLOG("device is already built-in");
} else {
internalizeDevice(childEntry);
}
break;
if (code == classCode::SATADevice || code == classCode::NVMeDevice || code == classCode::RAIDDevice){
DBGLOG("found storage device %s with class code 0x%x", childEntry->getName(), code);
// Always process device, even if built-in exists (for consistency)
internalizeDevice(childEntry);
// Don't break - continue processing other devices in same bridge
}
if (code == classCode::PCIBridge) {
else if (code == classCode::PCIBridge) {
DBGLOG("found bridge %s", childEntry->getName());
while (OSDynamicCast(OSBoolean, childEntry->getProperty("IOPCIConfigured")) != kOSBooleanTrue) {
DBGLOG("waiting for PCI bridge to be configured");
// Wait for bridge configuration with timeout
int timeout = 1000; // 10 second timeout (1000 iterations of 10ms each)
while (OSDynamicCast(OSBoolean, childEntry->getProperty("IOPCIConfigured")) != kOSBooleanTrue && timeout-- > 0) {
DBGLOG("waiting for PCI bridge to be configured (timeout: %d)", timeout);
IOSleep(10);
}
recurseBridge(childEntry);
if (timeout > 0) {
recurseBridge(childEntry);
} else {
DBGLOG("timeout waiting for bridge %s configuration", childEntry->getName());
}
}
}
}
Expand All @@ -114,70 +117,115 @@ void Innie::recurseBridge(IORegistryEntry *entry) {
}

void Innie::internalizeDevice(IORegistryEntry *entry) {
DBGLOG("adding built-in property");
DBGLOG("processing device %s for internalization", entry->getName());

// Always set built-in property (force override if exists)
setBuiltIn(entry);

while (OSDynamicCast(OSBoolean, entry->getProperty("IOPCIResourced")) != kOSBooleanTrue) {
DBGLOG("waiting for device to be resourced");
// Wait for device to be resourced with timeout
int timeout = 2000; // 20 second timeout (2000 iterations of 10ms each)
while (OSDynamicCast(OSBoolean, entry->getProperty("IOPCIResourced")) != kOSBooleanTrue && timeout-- > 0) {
DBGLOG("waiting for device to be resourced (timeout: %d)", timeout);
IOSleep(10);
}

// Proceed to update other properties
if (auto driverIterator = IORegistryIterator::iterateOver(entry, gIOServicePlane, kIORegistryIterateRecursively)) {
IORegistryEntry *driverEntry = nullptr;
while ((driverEntry = OSDynamicCast(IORegistryEntry, driverIterator->getNextObject())) != nullptr) {
DBGLOG("updating other properties");
updateOtherProperties(driverEntry);
if (timeout <= 0) {
DBGLOG("timeout waiting for device %s to be resourced", entry->getName());
return;
}

// Multiple passes to ensure all driver entries are updated
for (int pass = 0; pass < 3; pass++) {
DBGLOG("updating properties pass %d for device %s", pass + 1, entry->getName());

// Update properties on the device itself
updateOtherProperties(entry);

// Proceed to update driver entries in service plane
if (auto driverIterator = IORegistryIterator::iterateOver(entry, gIOServicePlane, kIORegistryIterateRecursively)) {
IORegistryEntry *driverEntry = nullptr;
while ((driverEntry = OSDynamicCast(IORegistryEntry, driverIterator->getNextObject())) != nullptr) {
if (driverEntry != entry) { // Don't update the same entry twice
DBGLOG("updating properties for driver entry %s", driverEntry->getName());
updateOtherProperties(driverEntry);
}
}
driverIterator->release();
}

// Wait between passes to allow driver loading
if (pass < 2) {
IOSleep(100);
}
driverIterator->release();
}

DBGLOG("completed internalization for device %s", entry->getName());
}

void Innie::setBuiltIn(IORegistryEntry *entry) {
if (entry) {
char dummy = '\0';
entry->setProperty("built-in", &dummy, 1);
// Use OSData with single byte value 0x01 for built-in property
if (auto builtInData = OSData::withBytes("\x01", 1)) {
DBGLOG("setting built-in property for %s", entry->getName());
entry->setProperty("built-in", builtInData);
builtInData->release();
}
}
}

void Innie::updateOtherProperties(IORegistryEntry *entry) {
if (entry) {
OSString *internal = OSString::withCString("Internal");
OSString *internalIcon = OSString::withCString("Internal.icns");

// Update icon
if (auto icon = entry->getProperty("IOMediaIcon")) {
if (auto dict = OSDynamicCast(OSDictionary, icon)) {
dict = OSDictionary::withDictionary(dict);
if (auto res = dict->getObject("IOBundleResourceFile")) {
dict->setObject("IOBundleResourceFile", internalIcon);
entry->setProperty("IOMediaIcon", dict);
}
if (!entry) return;

OSString *internal = OSString::withCString("Internal");
OSString *internalIcon = OSString::withCString("Internal.icns");

if (!internal || !internalIcon) {
if (internal) internal->release();
if (internalIcon) internalIcon->release();
return;
}

// Force update Physical Interconnect Location (always set, don't check if exists)
DBGLOG("setting Physical Interconnect Location to Internal for %s", entry->getName());
entry->setProperty("Physical Interconnect Location", internal);

// Update icon (always attempt to set)
if (auto icon = entry->getProperty("IOMediaIcon")) {
if (auto dict = OSDynamicCast(OSDictionary, icon)) {
dict = OSDictionary::withDictionary(dict);
if (dict) {
DBGLOG("updating IOMediaIcon for %s", entry->getName());
dict->setObject("IOBundleResourceFile", internalIcon);
entry->setProperty("IOMediaIcon", dict);
dict->release();
}
}

// Update interconnect
if (auto loc = entry->getProperty("Physical Interconnect Location")) {
if (auto prop = OSDynamicCast(OSString, loc)) {
entry->setProperty("Physical Interconnect Location", internal);
}
}

// Update update protocol characteristics
if (auto proto = entry->getProperty("Protocol Characteristics")) {
if (auto dict = OSDynamicCast(OSDictionary, proto)) {
dict = OSDictionary::withDictionary(dict);
if (auto loc = dict->getObject("Physical Interconnect Location")) {
if (auto prop = OSDynamicCast(OSString, loc)) {
dict->setObject("Physical Interconnect Location", internal);
entry->setProperty("Protocol Characteristics", dict);
}
}
}

// Update protocol characteristics (force update)
if (auto proto = entry->getProperty("Protocol Characteristics")) {
if (auto dict = OSDynamicCast(OSDictionary, proto)) {
dict = OSDictionary::withDictionary(dict);
if (dict) {
DBGLOG("updating Protocol Characteristics for %s", entry->getName());
dict->setObject("Physical Interconnect Location", internal);
entry->setProperty("Protocol Characteristics", dict);
dict->release();
}
}
internal->release();
internalIcon->release();
} else {
// Create Protocol Characteristics if it doesn't exist
DBGLOG("creating Protocol Characteristics for %s", entry->getName());
if (auto newDict = OSDictionary::withCapacity(1)) {
newDict->setObject("Physical Interconnect Location", internal);
entry->setProperty("Protocol Characteristics", newDict);
newDict->release();
}
}

// Also set built-in at this level for good measure
setBuiltIn(entry);

internal->release();
internalIcon->release();
}
1 change: 1 addition & 0 deletions Innie/Innie.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Innie : public IOService {
PCIBridge = 0x060400,
SATADevice = 0x010601,
NVMeDevice = 0x010802,
RAIDDevice = 0x010400, // RAID controller class code
};
};
};
Expand Down
1 change: 1 addition & 0 deletions MacKernelSDK
Submodule MacKernelSDK added at 179cbd
Loading