ManifestInternal.cpp [plain text]
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/uio.h>
#include <security_utilities/cfutilities.h>
#include <fts.h>
#include <fcntl.h>
#include <CommonCrypto/CommonDigest.h>
#include "Manifest.h"
ModuleNexus<CSSMInitializer> CSSMInitializer::mInstance;
CSSMInitializer::CSSMInitializer () : mModule (gGuidAppleCSP), mCSP (mModule)
{
}
CSSMInitializer::~CSSMInitializer ()
{
}
CssmClient::CSP* CSSMInitializer::GetCSP ()
{
return &mInstance().mCSP;
}
ManifestItemList::ManifestItemList ()
{
}
ManifestItemList::~ManifestItemList ()
{
iterator it = begin ();
while (it != end ())
{
delete *it++;
}
}
void ManifestItemList::DecodeURL (CFURLRef url, char *pathBuffer, CFIndex maxBufLen)
{
CFRef<CFStringRef> scheme (CFURLCopyScheme (url));
if (CFStringCompare (scheme, CFSTR("file"), 0) != 0)
{
MacOSError::throwMe (errSecManifestNotSupported);
}
if (!CFURLGetFileSystemRepresentation (url, false, (UInt8*) pathBuffer, maxBufLen))
{
MacOSError::throwMe (errSecManifestNotEqual);
}
}
void ManifestItemList::AddFileSystemObject (char* path, StringSet& exceptions, bool isRoot, bool hasAppleDoubleResourceFork)
{
StringSet::iterator it = exceptions.find (path);
if (it != exceptions.end ())
{
secinfo ("manifest", "Did not add %s to the manifest.", path);
return;
}
struct stat nodeStat;
int result = lstat (path, &nodeStat);
UnixError::check (result);
FileSystemEntryItem* mItem;
bool includeUserAndGroup = true;
switch (nodeStat.st_mode & S_IFMT)
{
case S_IFDIR: {
ManifestDirectoryItem* dirItem = new ManifestDirectoryItem ();
dirItem->SetPath (path, exceptions, isRoot);
mItem = dirItem;
}
break;
case S_IFREG:
{
ManifestFileItem* fileItem = new ManifestFileItem ();
fileItem->SetPath (path);
fileItem->ComputeRepresentations (nodeStat, hasAppleDoubleResourceFork);
mItem = fileItem;
}
break;
case S_IFLNK:
{
ManifestSymLinkItem* symItem = new ManifestSymLinkItem ();
symItem->SetPath (path);
symItem->ComputeRepresentation ();
mItem = symItem;
nodeStat.st_mode = S_IFLNK;
includeUserAndGroup = false;
}
break;
default:
{
ManifestOtherItem* otherItem = new ManifestOtherItem ();
otherItem->SetPath (path);
mItem = otherItem;
}
break;
}
if (includeUserAndGroup) {
mItem->SetUID (nodeStat.st_uid);
mItem->SetGID (nodeStat.st_gid);
}
mItem->SetMode (nodeStat.st_mode);
push_back (mItem);
}
void ManifestItemList::AddDataObject (CFDataRef object)
{
SHA1Digest digest;
CC_SHA1_CTX digestContext;
CC_SHA1_Init (&digestContext);
const UInt8* data = CFDataGetBytePtr (object);
CFIndex length = CFDataGetLength (object);
CC_SHA1_Update (&digestContext, data, (CC_LONG)length);
CC_SHA1_Final (digest, &digestContext);
ManifestDataBlobItem* db = new ManifestDataBlobItem ();
db->SetDigest (&digest);
db->SetLength (length);
push_back (db);
}
void ManifestItemList::ConvertToStringSet (const char* path, CFArrayRef exceptionList, StringSet &exceptions)
{
if (exceptionList != NULL)
{
std::string prefix = path;
if (prefix[prefix.length () - 1] != '/')
{
prefix += '/';
}
CFIndex max = CFArrayGetCount (exceptionList);
CFIndex n;
for (n = 0; n < max; ++n)
{
CFTypeRef dataRef = CFArrayGetValueAtIndex (exceptionList, n);
if (CFGetTypeID (dataRef) != CFStringGetTypeID ())
{
MacOSError::throwMe (errSecManifestInvalidException);
}
std::string s = prefix + cfString (CFStringRef (dataRef));
secinfo ("manifest", "Uncanonicalized path is %s", s.c_str ());
char realPath [PATH_MAX];
if (realpath (s.c_str (), realPath) != NULL)
{
secinfo ("manifest", "Inserted path %s as an exception", realPath);
exceptions.insert (realPath);
}
}
}
}
void ManifestItemList::AddObject (CFTypeRef object, CFArrayRef exceptionList)
{
CFTypeID objectID = CFGetTypeID (object);
if (objectID == CFDataGetTypeID ())
{
AddDataObject ((CFDataRef) object);
}
else if (objectID == CFURLGetTypeID ())
{
StringSet exceptions;
char path [PATH_MAX];
DecodeURL ((CFURLRef) object, path, sizeof (path));
char realPath [PATH_MAX];
if (realpath (path, realPath) == NULL)
{
UnixError::throwMe ();
}
ConvertToStringSet (realPath, exceptionList, exceptions);
AddFileSystemObject (realPath, exceptions, true, false);
}
else
{
MacOSError::throwMe (errSecManifestNotEqual);
}
}
void RootItemList::Compare (RootItemList& item, bool compareOwnerAndGroup)
{
unsigned numItems = (unsigned)size ();
if (numItems != item.size ())
{
MacOSError::throwMe (errSecManifestNotEqual);
}
unsigned i;
for (i = 0; i < numItems; ++i)
{
ManifestItem* item1 = (*this)[i];
ManifestItem* item2 = item[i];
if (item1->GetItemType () != item2->GetItemType ())
{
MacOSError::throwMe (errSecManifestNotEqual);
}
item1->Compare (item2, compareOwnerAndGroup);
}
}
class CompareManifestFileItems
{
public:
bool operator () (ManifestItem *a, ManifestItem *b);
};
bool CompareManifestFileItems::operator () (ManifestItem *a, ManifestItem *b)
{
FileSystemEntryItem *aa = static_cast<FileSystemEntryItem*>(a);
FileSystemEntryItem *bb = static_cast<FileSystemEntryItem*>(b);
return strcmp (aa->GetName (), bb->GetName ()) < 0;
}
void FileSystemItemList::Compare (FileSystemItemList &a, bool compareOwnerAndGroup)
{
unsigned numItems = (unsigned)size ();
if (numItems != a.size ())
{
MacOSError::throwMe (errSecManifestNotEqual);
}
sort (begin (), end (), CompareManifestFileItems ());
sort (a.begin (), a.end (), CompareManifestFileItems ());
unsigned i;
for (i = 0; i < numItems; ++i)
{
ManifestItem *thisListPtr = (*this)[i];
ManifestItem *aListPtr = a[i];
if (thisListPtr->GetItemType () != aListPtr->GetItemType ())
{
MacOSError::throwMe (errSecManifestNotEqual);
}
thisListPtr->Compare (aListPtr, compareOwnerAndGroup);
}
}
ManifestInternal::ManifestInternal ()
{
}
ManifestInternal::~ManifestInternal ()
{
secinfo ("manifest", "Destroyed manifest internal %p", this);
}
void ManifestInternal::CompareManifests (ManifestInternal& m1, ManifestInternal& m2, SecManifestCompareOptions options)
{
if ((options & ~kSecManifestVerifyOwnerAndGroup) != 0)
{
MacOSError::throwMe (errSecUnimplemented); }
m1.mManifestItems.Compare (m2.mManifestItems, (bool) options & kSecManifestVerifyOwnerAndGroup);
}
ManifestItem::~ManifestItem ()
{
}
ManifestDataBlobItem::ManifestDataBlobItem ()
{
}
ManifestDataBlobItem::~ManifestDataBlobItem ()
{
}
ManifestItemType ManifestDataBlobItem::GetItemType ()
{
return kManifestDataBlobItemType;
}
const SHA1Digest* ManifestDataBlobItem::GetDigest ()
{
return &mSHA1Digest;
}
void ManifestDataBlobItem::SetDigest (const SHA1Digest *sha1Digest)
{
memcpy (&mSHA1Digest, sha1Digest, sizeof (SHA1Digest));
}
size_t ManifestDataBlobItem::GetLength ()
{
return mLength;
}
void ManifestDataBlobItem::SetLength (size_t length)
{
mLength = length;
}
void ManifestDataBlobItem::Compare (ManifestItem* item, bool compareOwnerAndGroup)
{
ManifestDataBlobItem* i = static_cast<ManifestDataBlobItem*>(item);
if (memcmp (&i->mSHA1Digest, &mSHA1Digest, sizeof (SHA1Digest)) != 0)
{
MacOSError::throwMe (errSecManifestNotEqual);
}
}
FileSystemEntryItem::FileSystemEntryItem () : mUserID (0), mGroupID (0), mMode (0)
{
}
FileSystemEntryItem::~FileSystemEntryItem ()
{
}
void FileSystemEntryItem::SetName (char* name)
{
mName = name;
}
static char* StringTail (char* path)
{
char* finger = path + strlen (path) - 1;
while (finger != path && *finger != '/')
{
finger -= 1;
}
if (finger != path) {
finger += 1;
}
return finger;
}
void FileSystemEntryItem::SetPath (char* path)
{
mPath = path;
mName = StringTail (path);
secinfo ("manifest", "Created file item for %s with name %s", mPath.c_str (), mName.c_str ());
}
void FileSystemEntryItem::SetUID (uid_t uid)
{
mUserID = uid;
}
void FileSystemEntryItem::SetGID (gid_t gid)
{
mGroupID = gid;
}
void FileSystemEntryItem::SetMode (mode_t mode)
{
mMode = mode;
}
uid_t FileSystemEntryItem::GetUID () const
{
return mUserID;
}
gid_t FileSystemEntryItem::GetGID () const
{
return mGroupID;
}
mode_t FileSystemEntryItem::GetMode () const
{
return mMode;
}
const char* FileSystemEntryItem::GetName () const
{
return (char*) mName.c_str ();
}
void FileSystemEntryItem::Compare (ManifestItem *aa, bool compareOwnerAndGroup)
{
FileSystemEntryItem* a = static_cast<FileSystemEntryItem*>(aa);
if (mName != a->mName || mMode != a->mMode)
{
MacOSError::throwMe (errSecManifestNotEqual);
}
if (compareOwnerAndGroup)
{
if (mUserID != a->mUserID || mGroupID != a->mGroupID)
{
MacOSError::throwMe (errSecManifestNotEqual);
}
}
}
bool ManifestFileItem::FileSystemHasTrueForks (char* pathToFile)
{
struct statfs st;
int result = statfs (pathToFile, &st);
if (result != 0)
{
secinfo ("manifest", "Could not get statfs (error was %s)", strerror (errno));
UnixError::throwMe ();
}
return strcmp (st.f_fstypename, "afpfs") == 0 || strcmp (st.f_fstypename, "hfs") == 0;
}
std::string ManifestFileItem::ResourceFileName (char* path)
{
std::string filePath;
if (FileSystemHasTrueForks (path))
{
filePath = path;
return filePath + "/rsrc";
}
else
{
return "";
}
}
bool ManifestFileItem::HasResourceFork (char* pathToFile, std::string &result, struct stat &st)
{
result = ResourceFileName (pathToFile);
if (result.length () != 0)
{
int stresult = lstat (result.c_str (), &st);
if (stresult == 0)
{
return st.st_size != 0;
}
}
return false;
}
ManifestFileItem::ManifestFileItem () : mNumForks (1)
{
}
ManifestFileItem::~ManifestFileItem ()
{
secinfo ("manifest", "Destroyed manifest item %p for path %s", this, mPath.c_str ());
}
ManifestItemType ManifestFileItem::GetItemType ()
{
return kManifestFileItemType;
}
u_int32_t ManifestFileItem::GetNumberOfForks ()
{
return mNumForks;
}
void ManifestFileItem::SetNumberOfForks (u_int32_t numForks)
{
mNumForks = numForks;
}
bool ManifestFileItem::FileIsMachOBinary (char* path)
{
return false;
}
void ManifestFileItem::SetForkLength (int which, size_t length)
{
mFileLengths[which] = length;
}
size_t ManifestFileItem::GetForkLength (int which)
{
return mFileLengths[which];
}
void ManifestFileItem::ComputeRepresentations (struct stat &st, bool hasAppleDoubleResourceFork)
{
mNumForks = 1;
ComputeDigestForFile ((char*) mPath.c_str (), mDigest[0], mFileLengths[0], st);
struct stat stat2;
std::string resourceForkName;
if (hasAppleDoubleResourceFork)
{
mNumForks = 2;
resourceForkName = mPath;
int i = (int)resourceForkName.length () - 1;
while (i >= 0 && resourceForkName[i] != '/')
{
i -= 1;
}
i += 1;
resourceForkName.insert (i, "._");
ComputeDigestForAppleDoubleResourceFork ((char*) resourceForkName.c_str(), mDigest[1], mFileLengths[1]);
}
else if (HasResourceFork ((char*) mPath.c_str (), resourceForkName, stat2))
{
mNumForks = 2;
ComputeDigestForFile ((char*) resourceForkName.c_str (), mDigest[1], mFileLengths[1], stat2);
}
}
static const int kReadChunkSize = 4096 * 4;
static u_int32_t ExtractUInt32 (u_int8_t *&finger)
{
u_int32_t result = 0;
int i;
for (i = 0; i < 4; ++i)
{
result = (result << 8) | *finger++;
}
return result;
}
void ManifestFileItem::ComputeDigestForAppleDoubleResourceFork (char* name, SHA1Digest &digest, size_t &fileLength)
{
secinfo ("manifest", "Creating digest for AppleDouble resource fork %s", name);
CC_SHA1_CTX digestContext;
CC_SHA1_Init (&digestContext);
int fileNo = open (name, O_RDONLY, 0);
if (fileNo == -1)
{
UnixError::throwMe ();
}
struct stat st;
int result = fstat (fileNo, &st);
if (result == -1)
{
UnixError::throwMe ();
}
u_int8_t *buffer = new u_int8_t[st.st_size];
ssize_t bytesRead = read (fileNo, buffer, (size_t)st.st_size);
close (fileNo);
if (bytesRead != st.st_size)
{
delete[] buffer;
UnixError::throwMe ();
}
u_int8_t *bufPtr = buffer + 24;
int numEntries = (((int) bufPtr[0]) << 8) | (int) (bufPtr [1]);
bufPtr += 2;
ssize_t length = 0;
ssize_t offset = 0;
int i;
for (i = 0; i < numEntries; ++i)
{
ssize_t id = ExtractUInt32 (bufPtr);
offset = ExtractUInt32 (bufPtr);
length = ExtractUInt32 (bufPtr);
if (id == 2) {
break;
}
}
if (i >= numEntries) {
MacOSError::throwMe (errSecManifestNotSupported);
}
fileLength = length;
CC_SHA1_Update (&digestContext, buffer + offset, (CC_LONG)length);
CC_SHA1_Final (digest, &digestContext);
delete[] buffer;
}
void ManifestFileItem::ComputeDigestForFile (char* name, SHA1Digest &digest, size_t &fileLength, struct stat &st)
{
secinfo ("manifest", "Creating digest for %s", name);
CC_SHA1_CTX digestContext;
CC_SHA1_Init (&digestContext);
int fileNo = open (name, O_RDONLY, 0);
if (fileNo == -1)
{
UnixError::throwMe ();
}
fileLength = (size_t)st.st_size;
if (st.st_size != 0)
{
std::vector<char> buffer(kReadChunkSize);
ssize_t bytesRead;
while ((bytesRead = read (fileNo, buffer.data(), kReadChunkSize)) != 0)
{
CC_SHA1_Update (&digestContext, buffer.data(), (CC_LONG)bytesRead);
}
CC_SHA1_Final (digest, &digestContext);
}
close (fileNo);
}
void ManifestFileItem::GetItemRepresentation (int whichFork, void* &itemRep, size_t &size)
{
itemRep = (void*) &mDigest[whichFork];
size = kSHA1DigestSize;
}
void ManifestFileItem::SetItemRepresentation (int whichFork, const void* itemRep, size_t size)
{
memcpy ((void*) &mDigest[whichFork], itemRep, size);
}
void ManifestFileItem::Compare (ManifestItem *manifestItem, bool compareOwnerAndGroup)
{
FileSystemEntryItem::Compare (manifestItem, compareOwnerAndGroup);
ManifestFileItem* item = static_cast< ManifestFileItem*>(manifestItem);
secinfo ("manifest", "Comparing file item %s against %s", GetName (), item->GetName ());
if (mNumForks != item->mNumForks)
{
MacOSError::throwMe (errSecManifestNotEqual);
}
int i;
for (i = 0; i < mNumForks; ++i)
{
if (mFileLengths[i] != item->mFileLengths[i])
{
MacOSError::throwMe (errSecManifestNotEqual);
}
if (memcmp (&mDigest[i], item->mDigest[i], kSHA1DigestSize) != 0)
{
MacOSError::throwMe (errSecManifestNotEqual);
}
}
}
ManifestDirectoryItem::ManifestDirectoryItem ()
{
}
ManifestDirectoryItem::~ManifestDirectoryItem ()
{
secinfo ("manifest", "Destroyed directory item %p for path %s", this, mPath.c_str ());
}
const char* kAppleDoublePrefix = "._";
const int kAppleDoublePrefixLength = 2;
static int CompareFilenames (const FTSENT** a, const FTSENT** b)
{
const char* aa = (*a)->fts_name;
const char* bb = (*b)->fts_name;
bool aHasPrefix = false;
if (strncmp (aa, kAppleDoublePrefix, kAppleDoublePrefixLength) == 0) {
aHasPrefix = true;
aa += kAppleDoublePrefixLength;
}
if (strncmp (bb, kAppleDoublePrefix, kAppleDoublePrefixLength) == 0) {
bb += kAppleDoublePrefixLength;
}
int compare = strcmp (aa, bb);
if (compare == 0 && aHasPrefix)
{
return 1;
}
return compare;
}
const u_int8_t kAppleDoubleMagicNumber[] = {0x00, 0x05, 0x16, 0x07};
static bool PathIsAppleDoubleFile (const char* path)
{
int fRef = open (path, O_RDONLY, 0);
u_int8_t buffer[4];
ssize_t bytesRead = read(fRef, buffer, 4);
if (bytesRead == -1)
{
int err = errno;
close (fRef);
UnixError::throwMe (err);
}
close (fRef);
if (bytesRead != 4) {
return false;
}
int i;
for (i = 0; i < 4; ++i)
{
if (buffer[i] != kAppleDoubleMagicNumber[i])
{
return false;
}
}
return true;
}
void ManifestDirectoryItem::SetPath (char* path, StringSet &exceptions, bool isRoot)
{
if (isRoot)
{
mName = "/";
mPath = path;
}
else
{
FileSystemEntryItem::SetPath (path);
}
secinfo ("manifest", "Added directory entry for %s with name %s", mPath.c_str (), mName.c_str ());
char* path_argv[] = { path, NULL };
FTS* thisDir = fts_open (path_argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_NOSTAT | FTS_XDEV, CompareFilenames);
if (thisDir == NULL) {
UnixError::throwMe ();
}
(void)fts_read(thisDir);
FTSENT* dirEnt = fts_children (thisDir, FTS_NAMEONLY);
while (dirEnt != NULL)
{
FTSENT* dirEntNext = dirEnt->fts_link;
bool hasAppleDoubleResourceFork = false;
if (dirEntNext &&
strncmp (dirEntNext->fts_name, kAppleDoublePrefix, kAppleDoublePrefixLength) == 0 &&
strcmp (dirEnt->fts_name, dirEntNext->fts_name + kAppleDoublePrefixLength) == 0)
{
if (PathIsAppleDoubleFile ((mPath + "/" + dirEntNext->fts_name).c_str ()))
{
hasAppleDoubleResourceFork = true;
dirEntNext = dirEntNext->fts_link;
}
}
std::string fileName = mPath + "/" + dirEnt->fts_name;
mDirectoryItems.AddFileSystemObject ((char*) fileName.c_str(), exceptions, false, hasAppleDoubleResourceFork);
dirEnt = dirEntNext;
}
fts_close(thisDir);
}
ManifestItemType ManifestDirectoryItem::GetItemType ()
{
return kManifestDirectoryItemType;
}
void ManifestDirectoryItem::Compare (ManifestItem* a, bool compareOwnerAndGroup)
{
FileSystemEntryItem::Compare (a, compareOwnerAndGroup);
ManifestDirectoryItem* aa = static_cast<ManifestDirectoryItem*>(a);
secinfo ("manifest", "Comparing directory item %s against %s", GetName (), aa->GetName ());
mDirectoryItems.Compare (aa->mDirectoryItems, compareOwnerAndGroup);
}
ManifestSymLinkItem::ManifestSymLinkItem ()
{
}
ManifestSymLinkItem::~ManifestSymLinkItem ()
{
secinfo ("manifest", "Destroyed symlink item for %s", mPath.c_str ());
}
void ManifestSymLinkItem::ComputeRepresentation ()
{
char path [FILENAME_MAX];
int result = (int)readlink (mPath.c_str (), path, sizeof (path));
secinfo ("manifest", "Read content %s for %s", path, mPath.c_str ());
CC_SHA1_CTX digestContext;
CC_SHA1_Init (&digestContext);
CC_SHA1_Update (&digestContext, path, result);
CC_SHA1_Final (mDigest, &digestContext);
UnixError::check (result);
}
const SHA1Digest* ManifestSymLinkItem::GetDigest ()
{
return &mDigest;
}
void ManifestSymLinkItem::SetDigest (const SHA1Digest* digest)
{
memcpy (mDigest, digest, sizeof (SHA1Digest));
}
ManifestItemType ManifestSymLinkItem::GetItemType ()
{
return kManifestSymLinkItemType;
}
void ManifestSymLinkItem::Compare (ManifestItem *a, bool compareOwnerAndGroup)
{
FileSystemEntryItem::Compare (a, compareOwnerAndGroup);
ManifestSymLinkItem* aa = static_cast<ManifestSymLinkItem*>(a);
secinfo ("manifest", "Comparing symlink item %s against %s", GetName (), aa->GetName ());
if (memcmp (&mDigest, &aa->mDigest, kSHA1DigestSize) != 0)
{
MacOSError::throwMe (errSecManifestNotEqual);
}
}
ManifestOtherItem::ManifestOtherItem ()
{
}
ManifestOtherItem::~ManifestOtherItem ()
{
secinfo ("manifest", "Destroyed other item for path %s", mPath.c_str ());
}
ManifestItemType ManifestOtherItem::GetItemType ()
{
return kManifestOtherType;
}
void ManifestOtherItem::Compare (ManifestItem *a, bool compareOwnerAndGroup)
{
FileSystemEntryItem::Compare (a, compareOwnerAndGroup);
secinfo ("manifest", "Comparing other item %s against %s", GetName (), static_cast<FileSystemEntryItem*>(a)->GetName ());
}