secsecstaticcodeapitest.c   [plain text]


//
//  secsecstaticcodeapitest.c
//  secsecstaticcodeapitest
//

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <AssertMacros.h>
#include <sys/xattr.h>
#include <Security/SecCodePriv.h>
#include <Security/SecCode.h>
#include <Security/SecStaticCode.h>

#define BEGIN()                                             \
({                                                          \
    fprintf(stdout, "[BEGIN] %s\n", __FUNCTION__);          \
})

#define INFO(fmt, ...)                                      \
({                                                          \
    fprintf(stdout, fmt "\n", ##__VA_ARGS__);               \
})

#define PASS(fmt, ...)                                                      \
({                                                                          \
    fprintf(stdout, "[PASS] %s " fmt "\n", __FUNCTION__, ##__VA_ARGS__);    \
})

#define FAIL(fmt, ...)                                                      \
({                                                                          \
    fprintf(stdout, "[FAIL] %s " fmt "\n", __FUNCTION__, ##__VA_ARGS__);    \
})

#define SAFE_RELEASE(x)                                     \
({                                                          \
    if (x) {                                                \
        CFRelease(x);                                       \
        x = NULL;                                           \
    }                                                       \
})

#define kCommandRedirectOutputToDevNULL " >/dev/null 2>&1"
#define BUILD_COMMAND(x)                x kCommandRedirectOutputToDevNULL

#define kFAT32DiskImageFileDirectory    "/tmp"
#define kFAT32DiskImageFileName         "Security_SecStaticCodeAPITest.dmg"
#define kFAT32DiskImageFilePath         kFAT32DiskImageFileDirectory "/" kFAT32DiskImageFileName

#define kFAT32DiskImageVolumeDirectory  "/Volumes"
#define kFAT32DiskImageVolumeName       "SEC_TEST"
#define kFAT32DiskImageVolumePath        kFAT32DiskImageVolumeDirectory "/" kFAT32DiskImageVolumeName

#define kApplicationsPath               "/Applications"
#define kSafariBundleName               "Safari.app"
#define kSafariBundleOnSystemPath       kApplicationsPath "/" kSafariBundleName
#define kSafariBundleOnVolumePath       kFAT32DiskImageVolumePath "/" kSafariBundleName

static void
_cleanUpFAT32DiskImage(void)
{
    // Delete disk image.
    const char *command = BUILD_COMMAND("rm -rf " kFAT32DiskImageFilePath);
    system(command);

    // Detach volume.
    command = BUILD_COMMAND("hdiutil detach " kFAT32DiskImageVolumePath);
    system(command);
}

static int
_createFAT32DiskImage(void)
{
    const char *command = BUILD_COMMAND("hdiutil create -fs FAT32 -size 256m -volname " kFAT32DiskImageVolumeName " " kFAT32DiskImageFilePath);
    return system(command);
}

static int
_attachFAT32DiskImage(void)
{
    const char *command = BUILD_COMMAND("hdiutil attach " kFAT32DiskImageFilePath);
    return system(command);
}

static int
_copySafariBundleToVolume(void)
{
    const char *command = BUILD_COMMAND("cp -R " kSafariBundleOnSystemPath " " kSafariBundleOnVolumePath);
    return system(command);
}

static int
_confirmValidationPolicy(const char *path)
{
    int ret = -1;
    SecStaticCodeRef codeRef = NULL;
    SecCSFlags createFlags = kSecCSDefaultFlags;
    SecCSFlags validateFlags = kSecCSDefaultFlags | kSecCSStrictValidateStructure;

    CFStringRef stringRef = NULL;
    CFURLRef pathRef = NULL;

    require(path, done);

    stringRef = CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)path, strlen(path), kCFStringEncodingASCII, false);
    require(stringRef, done);

    pathRef = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, stringRef, kCFURLPOSIXPathStyle, false);
    require(pathRef, done);

    OSStatus status = SecStaticCodeCreateWithPath(pathRef, createFlags, &codeRef);
    require_noerr(status, done);
    require(codeRef, done);

    // Validate binary without kSecCSSkipXattrFiles. Expectation is this should fail.
    status = SecStaticCodeCheckValidity(codeRef, validateFlags, NULL);
    if (!status) {
        INFO("%s validated without kSecCSSkipXattrFiles flag", path);
        goto done;
    }
    SAFE_RELEASE(codeRef);

    // Create codeRef again to clear state.
    status = SecStaticCodeCreateWithPath(pathRef, createFlags, &codeRef);
    require_noerr(status, done);
    require(codeRef, done);

    // Validate binary with kSecCSSkipXattrFiles. Expectation is this should pass.
    validateFlags |= kSecCSSkipXattrFiles;
    status = SecStaticCodeCheckValidity(codeRef, validateFlags, NULL);
    if (status) {
        INFO("%s is not valid even with kSecCSSkipXattrFiles flag", path);
        goto done;
    }

    // Complete.
    ret = 0;

done:
    SAFE_RELEASE(codeRef);
    SAFE_RELEASE(stringRef);
    SAFE_RELEASE(pathRef);
    return ret;
}

static int
CheckCheckValidity_kSecCSSkipXattrFiles(void)
{
    BEGIN();

    int ret = -1;

    // Create FAT32 disk image.
    if (_createFAT32DiskImage()) {
        FAIL("_createFAT32DiskImage error");
        goto done;
    }
    INFO("Created " kFAT32DiskImageFilePath);

    // Attach disk image to the system.
    if (_attachFAT32DiskImage()) {
        FAIL("_attachFAT32DiskImage error");
        goto done;
    }
    INFO("Attached " kFAT32DiskImageFilePath " as " kFAT32DiskImageVolumePath);

    // Copy Safari.app to the attached volume.
    if (_copySafariBundleToVolume()) {
        FAIL("_copySafariBundleToVolume error");
        goto done;
    }
    INFO("Copied " kSafariBundleOnSystemPath " to " kSafariBundleOnVolumePath);

    // Write "com.apple.dummy" xattr to Safari.
    const char *xattrName = "com.apple.dummy";
    const uint32_t xattrValue = 0;

    const char *safariBinary = kSafariBundleOnVolumePath "/Contents/MacOS/Safari";
    if (setxattr(safariBinary, xattrName, &xattrValue, sizeof(xattrValue), 0, 0)) {
        FAIL("%s setxattr error: %d [%s]", safariBinary, errno, strerror(errno));
        goto done;
    }
    INFO("Wrote xattr \'%s\' to %s", xattrName, safariBinary);

    const char *safariCodeResources = kSafariBundleOnVolumePath "/Contents/_CodeSignature/CodeResources";
    if (setxattr(safariCodeResources, xattrName, &xattrValue, sizeof(xattrValue), 0, 0)) {
        FAIL("%s setxattr error: %d [%s]", safariCodeResources, errno, strerror(errno));
        goto done;
    }
    INFO("Wrote xattr \'%s\' to %s", xattrName, safariCodeResources);

    // Validate Safari.app with and without kSecCSSkipXattrFiles flag.
    if (_confirmValidationPolicy(kSafariBundleOnVolumePath)) {
        FAIL("%s _confirmValidationPolicy error", kSafariBundleOnVolumePath);
        goto done;
    }
    INFO("Validation policy on %s confirmed", kSafariBundleOnVolumePath);

    PASS("Completed validation policy check with kSecCSSkipXattrFiles");
    ret = 0;

done:
    _cleanUpFAT32DiskImage();
    return ret;
}

int main(void)
{
    fprintf(stdout, "[TEST] secsecstaticcodeapitest\n");

    int i;
    int (*testList[])(void) = {
        CheckCheckValidity_kSecCSSkipXattrFiles
    };
    const int numberOfTests = sizeof(testList) / sizeof(*testList);
    int testResults[numberOfTests] = {0};

    for (i = 0; i < numberOfTests; i++) {
        testResults[i] = testList[i]();
    }

    fprintf(stdout, "[SUMMARY]\n");
    for (i = 0; i < numberOfTests; i++) {
        fprintf(stdout, "%d. %s\n", i+1, testResults[i] == 0 ? "Passed" : "Failed");
    }

    return 0;
}