diff --git a/mozglue/linker/Logging.h b/mozglue/linker/Logging.h new file mode 100644 index 000000000000..ed0f51a4a034 --- /dev/null +++ b/mozglue/linker/Logging.h @@ -0,0 +1,22 @@ +/* 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/. */ + +#ifndef Logging_h +#define Logging_h + +#ifdef ANDROID +#include +#define log(...) __android_log_print(ANDROID_LOG_ERROR, "GeckoLinker", __VA_ARGS__) +#else +#include +#define log(format, ...) fprintf(stderr, format "\n", ##__VA_ARGS__) +#endif + +#ifdef MOZ_DEBUG_LINKER +#define debug log +#else +#define debug(...) +#endif + +#endif /* Logging_h */ diff --git a/mozglue/linker/Makefile.in b/mozglue/linker/Makefile.in new file mode 100644 index 000000000000..7dc0942da228 --- /dev/null +++ b/mozglue/linker/Makefile.in @@ -0,0 +1,21 @@ +# 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/. + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = mozglue +LIBRARY_NAME = linker +FORCE_STATIC_LIB= 1 +STL_FLAGS = + +CPPSRCS = \ + Zip.cpp \ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/mozglue/linker/Utils.h b/mozglue/linker/Utils.h new file mode 100644 index 000000000000..6fad29bd2691 --- /dev/null +++ b/mozglue/linker/Utils.h @@ -0,0 +1,93 @@ +/* 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/. */ + +#ifndef Utils_h +#define Utils_h + +#include + +/** + * On architectures that are little endian and that support unaligned reads, + * we can use direct type, but on others, we want to have a special class + * to handle conversion and alignment issues. + */ +#if defined(__i386__) || defined(__x86_64__) +typedef uint16_t le_uint16; +typedef uint32_t le_uint32; +#else + +/** + * Template that allows to find an unsigned int type from a (computed) bit size + */ +template struct UInt { }; +template <> struct UInt<16> { typedef uint16_t Type; }; +template <> struct UInt<32> { typedef uint32_t Type; }; + +/** + * Template to read 2 n-bit sized words as a 2*n-bit sized word, doing + * conversion from little endian and avoiding alignment issues. + */ +template +class le_to_cpu +{ +public: + operator typename UInt<16 * sizeof(T)>::Type() const + { + return (b << (sizeof(T) * 8)) | a; + } +private: + T a, b; +}; + +/** + * Type definitions + */ +typedef le_to_cpu le_uint16; +typedef le_to_cpu le_uint32; +#endif + +/** + * AutoCloseFD is a RAII wrapper for POSIX file descriptors + */ +class AutoCloseFD +{ +public: + AutoCloseFD(): fd(-1) { } + AutoCloseFD(int fd): fd(fd) { } + ~AutoCloseFD() + { + if (fd != -1) + close(fd); + } + + operator int() const + { + return fd; + } + + int forget() + { + int _fd = fd; + fd = -1; + return _fd; + } + + bool operator ==(int other) const + { + return fd == other; + } + + int operator =(int other) + { + if (fd != -1) + close(fd); + fd = other; + return fd; + } + +private: + int fd; +}; + +#endif /* Utils_h */ diff --git a/mozglue/linker/Zip.cpp b/mozglue/linker/Zip.cpp new file mode 100644 index 000000000000..d08ca6ba747d --- /dev/null +++ b/mozglue/linker/Zip.cpp @@ -0,0 +1,180 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include "Logging.h" +#include "Zip.h" + +Zip::Zip(const char *filename, ZipCollection *collection) +: name(strdup(filename)) +, mapped(MAP_FAILED) +, nextDir(NULL) +, entries(NULL) +, parent(collection) +{ + /* Open and map the file in memory */ + AutoCloseFD fd(open(name, O_RDONLY)); + if (fd == -1) { + log("Error opening %s: %s", filename, strerror(errno)); + return; + } + struct stat st; + if (fstat(fd, &st) == -1) { + log("Error stating %s: %s", filename, strerror(errno)); + return; + } + size = st.st_size; + if (size <= sizeof(CentralDirectoryEnd)) { + log("Error reading %s: too short", filename); + return; + } + mapped = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (mapped == MAP_FAILED) { + log("Error mmapping %s: %s", filename, strerror(errno)); + return; + } + debug("Mapped %s @%p", filename, mapped); + + /* Store the first Local File entry */ + nextFile = LocalFile::validate(mapped); +} + +Zip::~Zip() +{ + if (parent) + parent->Forget(this); + if (mapped != MAP_FAILED) { + munmap(mapped, size); + debug("Unmapped %s @%p", name, mapped); + } + free(name); +} + +bool +Zip::GetStream(const char *path, Zip::Stream *out) const +{ + debug("%s - GetFile %s", name, path); + /* Fast path: if the Local File header on store matches, we can return the + * corresponding stream right away. + * However, the Local File header may not contain enough information, in + * which case the 3rd bit on the generalFlag is set. Unfortunately, this + * bit is also set in some archives even when we do have the data (most + * notably the android packages as built by the Mozilla build system). + * So instead of testing the generalFlag bit, only use the fast path when + * we haven't read the central directory entries yet, and when the + * compressed size as defined in the header is not filled (which is a + * normal condition for the bit to be set). */ + if (nextFile && nextFile->GetName().Equals(path) && + !entries && (nextFile->compressedSize != 0)) { + debug("%s - %s was next file: fast path", name, path); + /* Fill Stream info from Local File header content */ + const char *data = reinterpret_cast(nextFile->GetData()); + out->compressedBuf = data; + out->compressedSize = nextFile->compressedSize; + out->uncompressedSize = nextFile->uncompressedSize; + out->type = static_cast(uint16_t(nextFile->compression)); + + /* Find the next Local File header. It is usually simply following the + * compressed stream, but in cases where the 3rd bit of the generalFlag + * is set, there is a Data Descriptor header before. */ + data += nextFile->compressedSize; + if ((nextFile->generalFlag & 0x8) && DataDescriptor::validate(data)) { + data += sizeof(DataDescriptor); + } + nextFile = LocalFile::validate(data); + return true; + } + + /* If the directory entry we have in store doesn't match, scan the Central + * Directory for the entry corresponding to the given path */ + if (!nextDir || !nextDir->GetName().Equals(path)) { + const DirectoryEntry *entry = GetFirstEntry(); + debug("%s - Scan directory entries in search for %s", name, path); + while (entry && !entry->GetName().Equals(path)) { + entry = entry->GetNext(); + } + nextDir = entry; + } + if (!nextDir) { + debug("%s - Couldn't find %s", name, path); + return false; + } + + /* Find the Local File header corresponding to the Directory entry that + * was found. */ + nextFile = LocalFile::validate(static_cast(mapped) + + nextDir->offset); + if (!nextFile) { + log("%s - Couldn't find the Local File header for %s", name, path); + return false; + } + + /* Fill Stream info from Directory entry content */ + const char *data = reinterpret_cast(nextFile->GetData()); + out->compressedBuf = data; + out->compressedSize = nextDir->compressedSize; + out->uncompressedSize = nextDir->uncompressedSize; + out->type = static_cast(uint16_t(nextDir->compression)); + + /* Store the next directory entry */ + nextDir = nextDir->GetNext(); + nextFile = NULL; + return true; +} + +const Zip::DirectoryEntry * +Zip::GetFirstEntry() const +{ + if (entries || mapped == MAP_FAILED) + return entries; // entries is NULL in the second case above + + const CentralDirectoryEnd *end = NULL; + const char *_end = static_cast(mapped) + size + - sizeof(CentralDirectoryEnd); + + /* Scan for the Central Directory End */ + for (; _end > mapped && !end; _end--) + end = CentralDirectoryEnd::validate(_end); + if (!end) { + log("%s - Couldn't find end of central directory record", name); + return NULL; + } + + entries = DirectoryEntry::validate(static_cast(mapped) + + end->offset); + if (!entries) { + log("%s - Couldn't find central directory record", name); + } + return entries; +} + +mozilla::TemporaryRef +ZipCollection::GetZip(const char *path) +{ + /* Search the list of Zips we already have for a match */ + for (std::vector::iterator it = zips.begin(); it < zips.end(); ++it) { + if (strcmp((*it)->GetName(), path) == 0) + return *it; + } + Zip *zip = new Zip(path, this); + zips.push_back(zip); + return zip; +} + +void +ZipCollection::Forget(Zip *zip) +{ + debug("ZipCollection::Forget(\"%s\")", zip->GetName()); + std::vector::iterator it = std::find(zips.begin(), zips.end(), zip); + if (*it == zip) + zips.erase(it); + else + debug("ZipCollection::Forget: didn't find \"%s\" in bookkeeping", zip->GetName()); +} diff --git a/mozglue/linker/Zip.h b/mozglue/linker/Zip.h new file mode 100644 index 000000000000..c4de36a528dd --- /dev/null +++ b/mozglue/linker/Zip.h @@ -0,0 +1,311 @@ +/* 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/. */ + +#ifndef Zip_h +#define Zip_h + +#include +#include +#include +#include "Utils.h" +#include "mozilla/RefPtr.h" + +/** + * Forward declaration + */ +class ZipCollection; + +/** + * Class to handle access to Zip archive streams. The Zip archive is mapped + * in memory, and streams are direct references to that mapped memory. + * Zip files are assumed to be correctly formed. No boundary checks are + * performed, which means hand-crafted malicious Zip archives can make the + * code fail in bad ways. However, since the only intended use is to load + * libraries from Zip archives, there is no interest in making this code + * safe, since the libraries could contain malicious code anyways. + */ +class Zip: public mozilla::RefCounted +{ +public: + /** + * Create a Zip instance for the given file name. In case of error, the + * Zip instance is still created but methods will error out. + */ + Zip(const char *filename, ZipCollection *collection = NULL); + + /** + * Destructor + */ + ~Zip(); + + /** + * Class used to access Zip archive item streams + */ + class Stream + { + public: + /** + * Stream types + */ + enum Type { + STORE = 0, + DEFLATE = 8 + }; + + /** + * Constructor + */ + Stream(): compressedBuf(NULL), compressedSize(0), uncompressedSize(0) + , type(STORE) { } + + const void *GetBuffer() { return compressedBuf; } + size_t GetSize() { return compressedSize; } + size_t GetUncompressedSize() { return uncompressedSize; } + Type GetType() { return type; } + + protected: + friend class Zip; + const void *compressedBuf; + size_t compressedSize; + size_t uncompressedSize; + Type type; + }; + + /** + * Returns a stream from the Zip archive. + */ + bool GetStream(const char *path, Stream *out) const; + + /** + * Returns the file name of the archive + */ + const char *GetName() const + { + return name; + } + +private: + /* File name of the archive */ + char *name; + /* Address where the Zip archive is mapped */ + void *mapped; + /* Size of the archive */ + size_t size; + + /** + * Strings (file names, comments, etc.) in the Zip headers are NOT zero + * terminated. This class is a helper around them. + */ + class StringBuf + { + public: + /** + * Constructor + */ + StringBuf(const char *buf, size_t length): buf(buf), length(length) { } + + /** + * Returns whether the string has the same content as the given zero + * terminated string. + */ + bool Equals(const char *str) const + { + return strncmp(str, buf, length) == 0; + } + + private: + const char *buf; + size_t length; + }; + +/* All the following types need to be packed */ +#pragma pack(1) + /** + * A Zip archive is an aggregate of entities which all start with a + * signature giving their type. This template is to be used as a base + * class for these entities. + */ + template + class SignedEntity + { + public: + /** + * Equivalent to reinterpret_cast(buf), with an additional + * check of the signature. + */ + static const T *validate(const void *buf) + { + const T *ret = static_cast(buf); + if (ret->signature == T::magic) + return ret; + return NULL; + } + private: + le_uint32 signature; + }; + + /** + * Header used to describe a Local File entry. The header is followed by + * the file name and an extra field, then by the data stream. + */ + struct LocalFile: public SignedEntity + { + /* Signature for a Local File header */ + static const uint32_t magic = 0x04034b50; + + /** + * Returns the file name + */ + StringBuf GetName() const + { + return StringBuf(reinterpret_cast(this) + sizeof(*this), + filenameSize); + } + + /** + * Returns a pointer to the data associated with this header + */ + const void *GetData() const + { + return reinterpret_cast(this) + sizeof(*this) + + filenameSize + extraFieldSize; + } + + le_uint16 minVersion; + le_uint16 generalFlag; + le_uint16 compression; + le_uint16 lastModifiedTime; + le_uint16 lastModifiedDate; + le_uint32 CRC32; + le_uint32 compressedSize; + le_uint32 uncompressedSize; + le_uint16 filenameSize; + le_uint16 extraFieldSize; + }; + + /** + * In some cases, when a zip archive is created, compressed size and CRC + * are not known when writing the Local File header. In these cases, the + * 3rd bit of the general flag in the Local File header is set, and there + * is an additional header following the compressed data. + */ + struct DataDescriptor: public SignedEntity + { + /* Signature for a Data Descriptor header */ + static const uint32_t magic = 0x08074b50; + + le_uint32 CRC32; + le_uint32 compressedSize; + le_uint32 uncompressedSize; + }; + + /** + * Header used to describe a Central Directory Entry. The header is + * followed by the file name, an extra field, and a comment. + */ + struct DirectoryEntry: public SignedEntity + { + /* Signature for a Central Directory Entry header */ + static const uint32_t magic = 0x02014b50; + + /** + * Returns the file name + */ + StringBuf GetName() const + { + return StringBuf(reinterpret_cast(this) + sizeof(*this), + filenameSize); + } + + /** + * Returns the Central Directory Entry following this one. + */ + const DirectoryEntry *GetNext() const + { + return validate(reinterpret_cast(this) + sizeof(*this) + + filenameSize + extraFieldSize + fileCommentSize); + } + + le_uint16 creatorVersion; + le_uint16 minVersion; + le_uint16 generalFlag; + le_uint16 compression; + le_uint16 lastModifiedTime; + le_uint16 lastModifiedDate; + le_uint32 CRC32; + le_uint32 compressedSize; + le_uint32 uncompressedSize; + le_uint16 filenameSize; + le_uint16 extraFieldSize; + le_uint16 fileCommentSize; + le_uint16 diskNum; + le_uint16 internalAttributes; + le_uint32 externalAttributes; + le_uint32 offset; + }; + + /** + * Header used to describe the End of Central Directory Record. + */ + struct CentralDirectoryEnd: public SignedEntity + { + /* Signature for the End of Central Directory Record */ + static const uint32_t magic = 0x06054b50; + + le_uint16 diskNum; + le_uint16 startDisk; + le_uint16 recordsOnDisk; + le_uint16 records; + le_uint32 size; + le_uint32 offset; + le_uint16 commentSize; + }; +#pragma pack() + + /** + * Returns the first Directory entry + */ + const DirectoryEntry *GetFirstEntry() const; + + /* Pointer to the Local File Entry following the last one GetStream() used. + * This is used by GetStream to avoid scanning the Directory Entries when the + * requested entry is that one. */ + mutable const LocalFile *nextFile; + + /* Likewise for the next Directory entry */ + mutable const DirectoryEntry *nextDir; + + /* Pointer to the Directory entries */ + mutable const DirectoryEntry *entries; + + /* ZipCollection containing this Zip */ + mutable ZipCollection *parent; +}; + +/** + * Class for bookkeeping Zip instances + */ +class ZipCollection +{ +public: + /** + * Get a Zip instance for the given path. If there is an existing one + * already, return that one, otherwise create a new one. + */ + mozilla::TemporaryRef GetZip(const char *path); + +protected: + /** + * Forget about the given Zip instance. This method is meant to be called + * by the Zip destructor. + */ + friend Zip::~Zip(); + void Forget(Zip *zip); + +private: + /* Zip instances bookkept in this collection */ + std::vector zips; +}; + +#endif /* Zip_h */