Resources: reject compressed content we can't decompress

This solution is composed of two features:

1) C++ code generated by RCC uses two symbols exported from QtCore that
are only present if the feature was compiled in. If the feature was not
compiled in, this will cause a linker error either at build time or at
load time (if they were functions, the error could be at runtime).

2) Binary files generated by RCC have a new header field containing
flags. We're currently using two flags, one for Zlib and one for
Zstandard.

This means we now have binary RCC format version 3.

Change-Id: I42a48bd64ccc41aebf84fffd156545fb6a4f72d9
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@qt.io>
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
This commit is contained in:
Thiago Macieira 2018-11-08 14:16:33 -08:00
parent c820e0b117
commit d20c980576
9 changed files with 136 additions and 30 deletions

View File

@ -77,6 +77,26 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
// Symbols used by code generated by RCC.
// They cause compilation errors if the RCC content couldn't
// be interpreted by this QtCore version.
#if defined(__ELF__) || defined(__APPLE__) // same as RCC generates
# define RCC_FEATURE_SYMBOL(feature) \
extern Q_CORE_EXPORT const quint8 qt_resourceFeature ## feature; \
const quint8 qt_resourceFeature ## feature = 0;
#else
# define RCC_FEATURE_SYMBOL(feature) \
Q_CORE_EXPORT quint8 qResourceFeature ## feature() { return 0; }
#endif
#ifndef QT_NO_COMPRESS
RCC_FEATURE_SYMBOL(Zlib)
#endif
#if QT_CONFIG(zstd)
RCC_FEATURE_SYMBOL(Zstd)
#endif
#undef RCC_FEATURE_SYMBOL
class QStringSplitter class QStringSplitter
{ {
@ -109,6 +129,7 @@ public:
//resource glue //resource glue
class QResourceRoot class QResourceRoot
{ {
public:
enum Flags enum Flags
{ {
// must match rcc.h // must match rcc.h
@ -116,6 +137,7 @@ class QResourceRoot
Directory = 0x02, Directory = 0x02,
CompressedZstd = 0x04 CompressedZstd = 0x04
}; };
private:
const uchar *tree, *names, *payloads; const uchar *tree, *names, *payloads;
int version; int version;
inline int findOffset(int node) const { return node * (14 + (version >= 0x02 ? 8 : 0)); } //sizeof each tree element inline int findOffset(int node) const { return node * (14 + (version >= 0x02 ? 8 : 0)); } //sizeof each tree element
@ -917,7 +939,7 @@ Q_CORE_EXPORT bool qRegisterResourceData(int version, const unsigned char *tree,
const unsigned char *name, const unsigned char *data) const unsigned char *name, const unsigned char *data)
{ {
QMutexLocker lock(resourceMutex()); QMutexLocker lock(resourceMutex());
if ((version == 0x01 || version == 0x2) && resourceList()) { if (version >= 0x01 && version <= 0x3 && resourceList()) {
bool found = false; bool found = false;
QResourceRoot res(version, tree, name, data); QResourceRoot res(version, tree, name, data);
for(int i = 0; i < resourceList()->size(); ++i) { for(int i = 0; i < resourceList()->size(); ++i) {
@ -943,7 +965,7 @@ Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tre
return false; return false;
QMutexLocker lock(resourceMutex()); QMutexLocker lock(resourceMutex());
if ((version == 0x01 || version == 0x02) && resourceList()) { if (version >= 0x01 && version <= 0x3 && resourceList()) {
QResourceRoot res(version, tree, name, data); QResourceRoot res(version, tree, name, data);
for(int i = 0; i < resourceList()->size(); ) { for(int i = 0; i < resourceList()->size(); ) {
if(*resourceList()->at(i) == res) { if(*resourceList()->at(i) == res) {
@ -1002,11 +1024,27 @@ public:
const int name_offset = qFromBigEndian<qint32>(b + offset); const int name_offset = qFromBigEndian<qint32>(b + offset);
offset += 4; offset += 4;
quint32 file_flags = 0;
if (version >= 3) {
file_flags = qFromBigEndian<qint32>(b + offset);
offset += 4;
}
// Some sanity checking for sizes. This is _not_ a security measure. // Some sanity checking for sizes. This is _not_ a security measure.
if (size >= 0 && (tree_offset >= size || data_offset >= size || name_offset >= size)) if (size >= 0 && (tree_offset >= size || data_offset >= size || name_offset >= size))
return false; return false;
if (version == 0x01 || version == 0x02) { // And some sanity checking for features
quint32 acceptableFlags = 0;
#ifndef QT_NO_COMPRESS
acceptableFlags |= Compressed;
#endif
if (QT_CONFIG(zstd))
acceptableFlags |= CompressedZstd;
if (file_flags & ~acceptableFlags)
return false;
if (version >= 0x01 && version <= 0x03) {
buffer = b; buffer = b;
setSource(version, b+tree_offset, b+name_offset, b+data_offset); setSource(version, b+tree_offset, b+name_offset, b+data_offset);
return true; return true;

View File

@ -185,13 +185,13 @@ int runRcc(int argc, char *argv[])
QString errorMsg; QString errorMsg;
quint8 formatVersion = 2; quint8 formatVersion = 3;
if (parser.isSet(formatVersionOption)) { if (parser.isSet(formatVersionOption)) {
bool ok = false; bool ok = false;
formatVersion = parser.value(formatVersionOption).toUInt(&ok); formatVersion = parser.value(formatVersionOption).toUInt(&ok);
if (!ok) { if (!ok) {
errorMsg = QLatin1String("Invalid format version specified"); errorMsg = QLatin1String("Invalid format version specified");
} else if (formatVersion != 1 && formatVersion != 2) { } else if (formatVersion < 1 || formatVersion > 3) {
errorMsg = QLatin1String("Unsupported format version specified"); errorMsg = QLatin1String("Unsupported format version specified");
} }
} }
@ -208,6 +208,8 @@ int runRcc(int argc, char *argv[])
if (parser.isSet(compressionAlgoOption)) if (parser.isSet(compressionAlgoOption))
library.setCompressionAlgorithm(RCCResourceLibrary::parseCompressionAlgorithm(parser.value(compressionAlgoOption), &errorMsg)); library.setCompressionAlgorithm(RCCResourceLibrary::parseCompressionAlgorithm(parser.value(compressionAlgoOption), &errorMsg));
if (formatVersion < 3 && library.compressionAlgorithm() == RCCResourceLibrary::CompressionAlgorithm::Zstd)
errorMsg = QLatin1String("Zstandard compression requires format version 3 or higher");
if (parser.isSet(nocompressOption)) if (parser.isSet(nocompressOption))
library.setCompressionAlgorithm(RCCResourceLibrary::CompressionAlgorithm::None); library.setCompressionAlgorithm(RCCResourceLibrary::CompressionAlgorithm::None);
if (parser.isSet(compressOption) && errorMsg.isEmpty()) { if (parser.isSet(compressOption) && errorMsg.isEmpty()) {

View File

@ -295,6 +295,7 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
lib.m_errorDevice->write(msg.toUtf8()); lib.m_errorDevice->write(msg.toUtf8());
} }
lib.m_overallFlags |= CompressedZstd;
m_flags |= CompressedZstd; m_flags |= CompressedZstd;
data = std::move(compressed); data = std::move(compressed);
data.truncate(n); data.truncate(n);
@ -321,6 +322,7 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
lib.m_errorDevice->write(msg.toUtf8()); lib.m_errorDevice->write(msg.toUtf8());
} }
data = compressed; data = compressed;
lib.m_overallFlags |= Compressed;
m_flags |= Compressed; m_flags |= Compressed;
} else if (lib.verbose()) { } else if (lib.verbose()) {
QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name);
@ -438,6 +440,7 @@ RCCResourceLibrary::RCCResourceLibrary(quint8 formatVersion)
m_treeOffset(0), m_treeOffset(0),
m_namesOffset(0), m_namesOffset(0),
m_dataOffset(0), m_dataOffset(0),
m_overallFlags(0),
m_useNameSpace(CONSTANT_USENAMESPACE), m_useNameSpace(CONSTANT_USENAMESPACE),
m_errorDevice(0), m_errorDevice(0),
m_outDevice(0), m_outDevice(0),
@ -945,6 +948,14 @@ bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIO
return true; return true;
} }
void RCCResourceLibrary::writeDecimal(int value)
{
Q_ASSERT(m_format != RCCResourceLibrary::Binary);
char buf[std::numeric_limits<int>::digits10 + 2];
int n = snprintf(buf, sizeof(buf), "%d", value);
write(buf, n + 1); // write() takes a size including terminating NUL
}
void RCCResourceLibrary::writeHex(quint8 tmp) void RCCResourceLibrary::writeHex(quint8 tmp)
{ {
const char digits[] = "0123456789abcdef"; const char digits[] = "0123456789abcdef";
@ -1039,6 +1050,8 @@ bool RCCResourceLibrary::writeHeader()
writeNumber4(0); writeNumber4(0);
writeNumber4(0); writeNumber4(0);
writeNumber4(0); writeNumber4(0);
if (m_formatVersion >= 3)
writeNumber4(m_overallFlags);
} }
return true; return true;
} }
@ -1241,10 +1254,35 @@ bool RCCResourceLibrary::writeInitializer()
if (m_root) { if (m_root) {
writeString("bool qRegisterResourceData" writeString("bool qRegisterResourceData"
"(int, const unsigned char *, " "(int, const unsigned char *, "
"const unsigned char *, const unsigned char *);\n\n"); "const unsigned char *, const unsigned char *);\n");
writeString("bool qUnregisterResourceData" writeString("bool qUnregisterResourceData"
"(int, const unsigned char *, " "(int, const unsigned char *, "
"const unsigned char *, const unsigned char *);\n\n"); "const unsigned char *, const unsigned char *);\n\n");
if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) {
// use variable relocations with ELF and Mach-O
writeString("#if defined(__ELF__) || defined(__APPLE__)\n");
if (m_overallFlags & RCCFileInfo::Compressed) {
writeString("static inline unsigned char qResourceFeatureZlib()\n"
"{\n"
" extern const unsigned char qt_resourceFeatureZlib;\n"
" return qt_resourceFeatureZlib;\n"
"}\n");
}
if (m_overallFlags & RCCFileInfo::CompressedZstd) {
writeString("static inline unsigned char qResourceFeatureZstd()\n"
"{\n"
" extern const unsigned char qt_resourceFeatureZstd;\n"
" return qt_resourceFeatureZstd;\n"
"}\n");
}
writeString("#else\n");
if (m_overallFlags & RCCFileInfo::Compressed)
writeString("unsigned char qResourceFeatureZlib();\n");
if (m_overallFlags & RCCFileInfo::CompressedZstd)
writeString("unsigned char qResourceFeatureZstd();\n");
writeString("#endif\n\n");
}
} }
if (m_useNameSpace) if (m_useNameSpace)
@ -1263,12 +1301,12 @@ bool RCCResourceLibrary::writeInitializer()
writeString("()\n{\n"); writeString("()\n{\n");
if (m_root) { if (m_root) {
writeString(" "); writeString(" int version = ");
writeDecimal(m_formatVersion);
writeString(";\n ");
writeAddNamespaceFunction("qRegisterResourceData"); writeAddNamespaceFunction("qRegisterResourceData");
writeString("\n ("); writeString("\n (version, qt_resource_struct, "
writeHex(m_formatVersion); "qt_resource_name, qt_resource_data);\n");
writeString(" qt_resource_struct, "
"qt_resource_name, qt_resource_data);\n");
} }
writeString(" return 1;\n"); writeString(" return 1;\n");
writeString("}\n\n"); writeString("}\n\n");
@ -1286,11 +1324,24 @@ bool RCCResourceLibrary::writeInitializer()
writeMangleNamespaceFunction(cleanResources); writeMangleNamespaceFunction(cleanResources);
writeString("()\n{\n"); writeString("()\n{\n");
if (m_root) { if (m_root) {
writeString(" "); writeString(" int version = ");
writeDecimal(m_formatVersion);
writeString(";\n ");
// ODR-use certain symbols from QtCore if we require optional features
if (m_overallFlags & RCCFileInfo::Compressed) {
writeString("version += ");
writeAddNamespaceFunction("qResourceFeatureZlib()");
writeString(";\n ");
}
if (m_overallFlags & RCCFileInfo::CompressedZstd) {
writeString("version += ");
writeAddNamespaceFunction("qResourceFeatureZstd()");
writeString(";\n ");
}
writeAddNamespaceFunction("qUnregisterResourceData"); writeAddNamespaceFunction("qUnregisterResourceData");
writeString("\n ("); writeString("\n (version, qt_resource_struct, "
writeHex(m_formatVersion);
writeString(" qt_resource_struct, "
"qt_resource_name, qt_resource_data);\n"); "qt_resource_name, qt_resource_data);\n");
} }
writeString(" return 1;\n"); writeString(" return 1;\n");
@ -1326,6 +1377,13 @@ bool RCCResourceLibrary::writeInitializer()
p[i++] = (m_namesOffset >> 16) & 0xff; p[i++] = (m_namesOffset >> 16) & 0xff;
p[i++] = (m_namesOffset >> 8) & 0xff; p[i++] = (m_namesOffset >> 8) & 0xff;
p[i++] = (m_namesOffset >> 0) & 0xff; p[i++] = (m_namesOffset >> 0) & 0xff;
if (m_formatVersion >= 3) {
p[i++] = (m_overallFlags >> 24) & 0xff;
p[i++] = (m_overallFlags >> 16) & 0xff;
p[i++] = (m_overallFlags >> 8) & 0xff;
p[i++] = (m_overallFlags >> 0) & 0xff;
}
} }
return true; return true;
} }

View File

@ -134,6 +134,7 @@ private:
bool writeInitializer(); bool writeInitializer();
void writeMangleNamespaceFunction(const QByteArray &name); void writeMangleNamespaceFunction(const QByteArray &name);
void writeAddNamespaceFunction(const QByteArray &name); void writeAddNamespaceFunction(const QByteArray &name);
void writeDecimal(int value);
void writeHex(quint8 number); void writeHex(quint8 number);
void writeNumber2(quint16 number); void writeNumber2(quint16 number);
void writeNumber4(quint32 number); void writeNumber4(quint32 number);
@ -160,6 +161,7 @@ private:
int m_treeOffset; int m_treeOffset;
int m_namesOffset; int m_namesOffset;
int m_dataOffset; int m_dataOffset;
quint32 m_overallFlags;
bool m_useNameSpace; bool m_useNameSpace;
QStringList m_failedResources; QStringList m_failedResources;
QIODevice *m_errorDevice; QIODevice *m_errorDevice;

View File

@ -115,7 +115,6 @@ namespace QT_NAMESPACE {
#endif #endif
bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *);
bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *);
#ifdef QT_NAMESPACE #ifdef QT_NAMESPACE
@ -125,16 +124,18 @@ bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *,
int QT_RCC_MANGLE_NAMESPACE(qInitResources)(); int QT_RCC_MANGLE_NAMESPACE(qInitResources)();
int QT_RCC_MANGLE_NAMESPACE(qInitResources)() int QT_RCC_MANGLE_NAMESPACE(qInitResources)()
{ {
int version = 3;
QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData) QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData)
(0x2, qt_resource_struct, qt_resource_name, qt_resource_data); (version, qt_resource_struct, qt_resource_name, qt_resource_data);
return 1; return 1;
} }
int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)(); int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)();
int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)() int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)()
{ {
int version = 3;
QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData) QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData)
(0x2, qt_resource_struct, qt_resource_name, qt_resource_data); (version, qt_resource_struct, qt_resource_name, qt_resource_data);
return 1; return 1;
} }

View File

@ -57,7 +57,6 @@ namespace QT_NAMESPACE {
#endif #endif
bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *);
bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *);
#ifdef QT_NAMESPACE #ifdef QT_NAMESPACE
@ -67,16 +66,18 @@ bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *,
int QT_RCC_MANGLE_NAMESPACE(qInitResources)(); int QT_RCC_MANGLE_NAMESPACE(qInitResources)();
int QT_RCC_MANGLE_NAMESPACE(qInitResources)() int QT_RCC_MANGLE_NAMESPACE(qInitResources)()
{ {
int version = 3;
QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData) QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData)
(0x2, qt_resource_struct, qt_resource_name, qt_resource_data); (version, qt_resource_struct, qt_resource_name, qt_resource_data);
return 1; return 1;
} }
int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)(); int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)();
int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)() int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)()
{ {
int version = 3;
QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData) QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData)
(0x2, qt_resource_struct, qt_resource_name, qt_resource_data); (version, qt_resource_struct, qt_resource_name, qt_resource_data);
return 1; return 1;
} }

View File

@ -58,7 +58,6 @@ namespace QT_NAMESPACE {
#endif #endif
bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *);
bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *);
#ifdef QT_NAMESPACE #ifdef QT_NAMESPACE
@ -68,16 +67,18 @@ bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *,
int QT_RCC_MANGLE_NAMESPACE(qInitResources)(); int QT_RCC_MANGLE_NAMESPACE(qInitResources)();
int QT_RCC_MANGLE_NAMESPACE(qInitResources)() int QT_RCC_MANGLE_NAMESPACE(qInitResources)()
{ {
int version = 3;
QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData) QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData)
(0x2, qt_resource_struct, qt_resource_name, qt_resource_data); (version, qt_resource_struct, qt_resource_name, qt_resource_data);
return 1; return 1;
} }
int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)(); int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)();
int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)() int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)()
{ {
int version = 3;
QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData) QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData)
(0x2, qt_resource_struct, qt_resource_name, qt_resource_data); (version, qt_resource_struct, qt_resource_name, qt_resource_data);
return 1; return 1;
} }

View File

@ -95,7 +95,6 @@ namespace QT_NAMESPACE {
#endif #endif
bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *);
bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *); bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *, const unsigned char *);
#ifdef QT_NAMESPACE #ifdef QT_NAMESPACE
@ -105,16 +104,18 @@ bool qUnregisterResourceData(int, const unsigned char *, const unsigned char *,
int QT_RCC_MANGLE_NAMESPACE(qInitResources)(); int QT_RCC_MANGLE_NAMESPACE(qInitResources)();
int QT_RCC_MANGLE_NAMESPACE(qInitResources)() int QT_RCC_MANGLE_NAMESPACE(qInitResources)()
{ {
int version = 3;
QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData) QT_RCC_PREPEND_NAMESPACE(qRegisterResourceData)
(0x2, qt_resource_struct, qt_resource_name, qt_resource_data); (version, qt_resource_struct, qt_resource_name, qt_resource_data);
return 1; return 1;
} }
int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)(); int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)();
int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)() int QT_RCC_MANGLE_NAMESPACE(qCleanupResources)()
{ {
int version = 3;
QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData) QT_RCC_PREPEND_NAMESPACE(qUnregisterResourceData)
(0x2, qt_resource_struct, qt_resource_name, qt_resource_data); (version, qt_resource_struct, qt_resource_name, qt_resource_data);
return 1; return 1;
} }

View File

@ -159,9 +159,10 @@ void tst_rcc::rcc()
return; return;
} }
// Launch // Launch; force no compression, otherwise the output would be different
// depending on the compression algorithm we're using
QProcess process; QProcess process;
process.start(m_rcc, QStringList(qrcfile)); process.start(m_rcc, { "-no-compress", qrcfile });
if (!process.waitForFinished()) { if (!process.waitForFinished()) {
const QString path = QString::fromLocal8Bit(qgetenv("PATH")); const QString path = QString::fromLocal8Bit(qgetenv("PATH"));
QString message = QString::fromLatin1("'%1' could not be found when run from '%2'. Path: '%3' "). QString message = QString::fromLatin1("'%1' could not be found when run from '%2'. Path: '%3' ").
@ -196,8 +197,9 @@ static void createRccBinaryData(const QString &rcc, const QString &baseDir,
QString currentDir = QDir::currentPath(); QString currentDir = QDir::currentPath();
QDir::setCurrent(baseDir); QDir::setCurrent(baseDir);
// same as above: force no compression
QProcess rccProcess; QProcess rccProcess;
rccProcess.start(rcc, QStringList() << "-binary" << "-o" << rccFileName << qrcFileName); rccProcess.start(rcc, { "-binary", "-no-compress", "-o", rccFileName, qrcFileName });
bool ok = rccProcess.waitForFinished(); bool ok = rccProcess.waitForFinished();
if (!ok) { if (!ok) {
QString errorString = QString::fromLatin1("Could not start rcc (is it in PATH?): %1").arg(rccProcess.errorString()); QString errorString = QString::fromLatin1("Could not start rcc (is it in PATH?): %1").arg(rccProcess.errorString());