Apple: Use POSIX IPC instead of System V in sandboxed applications
System V semaphores are not supported in sandboxed applications, so when Qt is configured with App Store compliance, or the user requests POSIX IPC explicitly, we use that instead. https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24 As the shared memory name limit on Apple platforms is very low, we have to skip the existing logic for naming, and instead use a truncated hash of the key. This should still be fine for avoiding any collisions in practice. An explicit check for the ENAMETOOLONG error has been added to catch any cases where they key goes beyond the allowed length. Sandboxed applications also have an extra requirement that the key must include an application group identifier. This requirement has been pushed up to the user and documented, as we don't have enough information in Qt to know which identifier to use. Both tst_QSystemSemaphore and tst_QSharedMemory work as before with both sandboxed and non-sandboxed applications, after removing some assumptions in tst_QSharedMemory about System V behavior. Fixes: QTBUG-91130 Change-Id: Iaf1edb36a5d84d69e42ec31471a48d112faa8c6a Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
20f835329a
commit
9661cde161
@ -593,8 +593,8 @@ qt_feature("inotify" PUBLIC PRIVATE
|
|||||||
qt_feature_definition("inotify" "QT_NO_INOTIFY" NEGATE VALUE "1")
|
qt_feature_definition("inotify" "QT_NO_INOTIFY" NEGATE VALUE "1")
|
||||||
qt_feature("ipc_posix"
|
qt_feature("ipc_posix"
|
||||||
LABEL "Using POSIX IPC"
|
LABEL "Using POSIX IPC"
|
||||||
AUTODETECT NOT WIN32
|
AUTODETECT NOT WIN32 AND ( ( APPLE AND QT_FEATURE_appstore_compliant ) OR NOT TEST_ipc_sysv )
|
||||||
CONDITION NOT TEST_ipc_sysv AND TEST_ipc_posix
|
CONDITION TEST_ipc_posix
|
||||||
)
|
)
|
||||||
qt_feature_definition("ipc_posix" "QT_POSIX_IPC")
|
qt_feature_definition("ipc_posix" "QT_POSIX_IPC")
|
||||||
qt_feature("journald" PRIVATE
|
qt_feature("journald" PRIVATE
|
||||||
|
@ -623,8 +623,8 @@
|
|||||||
},
|
},
|
||||||
"ipc_posix": {
|
"ipc_posix": {
|
||||||
"label": "Using POSIX IPC",
|
"label": "Using POSIX IPC",
|
||||||
"autoDetect": "!config.win32",
|
"autoDetect": "!config.win32 && ((config.darwin && features.appstore-compliant) || !tests.ipc_sysv)",
|
||||||
"condition": "!tests.ipc_sysv && tests.ipc_posix",
|
"condition": "tests.ipc_posix",
|
||||||
"output": [ { "type": "define", "name": "QT_POSIX_IPC" } ]
|
"output": [ { "type": "define", "name": "QT_POSIX_IPC" } ]
|
||||||
},
|
},
|
||||||
"journald": {
|
"journald": {
|
||||||
|
@ -396,9 +396,9 @@ AppleApplication *qt_apple_sharedApplication()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_MACOS) && !defined(QT_BOOTSTRAPPED)
|
|
||||||
bool qt_apple_isSandboxed()
|
bool qt_apple_isSandboxed()
|
||||||
{
|
{
|
||||||
|
#if defined(Q_OS_MACOS)
|
||||||
static bool isSandboxed = []() {
|
static bool isSandboxed = []() {
|
||||||
QCFType<SecStaticCodeRef> staticCode = nullptr;
|
QCFType<SecStaticCodeRef> staticCode = nullptr;
|
||||||
NSURL *executableUrl = NSBundle.mainBundle.executableURL;
|
NSURL *executableUrl = NSBundle.mainBundle.executableURL;
|
||||||
@ -418,8 +418,12 @@ bool qt_apple_isSandboxed()
|
|||||||
return true;
|
return true;
|
||||||
}();
|
}();
|
||||||
return isSandboxed;
|
return isSandboxed;
|
||||||
|
#else
|
||||||
|
return true; // All other Apple platforms
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(QT_BOOTSTRAPPED)
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
@implementation NSObject (QtSandboxHelpers)
|
@implementation NSObject (QtSandboxHelpers)
|
||||||
- (id)qt_valueForPrivateKey:(NSString *)key
|
- (id)qt_valueForPrivateKey:(NSString *)key
|
||||||
|
@ -196,16 +196,14 @@ Q_CORE_EXPORT QDebug operator<<(QDebug debug, const QCFString &string);
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
Q_CORE_EXPORT bool qt_apple_isApplicationExtension();
|
Q_CORE_EXPORT bool qt_apple_isApplicationExtension();
|
||||||
|
|
||||||
#if defined(Q_OS_MACOS) && !defined(QT_BOOTSTRAPPED)
|
|
||||||
Q_CORE_EXPORT bool qt_apple_isSandboxed();
|
Q_CORE_EXPORT bool qt_apple_isSandboxed();
|
||||||
# ifdef __OBJC__
|
|
||||||
|
#if !defined(QT_BOOTSTRAPPED) && defined(__OBJC__)
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
@interface NSObject (QtSandboxHelpers)
|
@interface NSObject (QtSandboxHelpers)
|
||||||
- (id)qt_valueForPrivateKey:(NSString *)key;
|
- (id)qt_valueForPrivateKey:(NSString *)key;
|
||||||
@end
|
@end
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
# endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !defined(QT_BOOTSTRAPPED) && !defined(Q_OS_WATCHOS)
|
#if !defined(QT_BOOTSTRAPPED) && !defined(Q_OS_WATCHOS)
|
||||||
|
@ -47,6 +47,17 @@
|
|||||||
# include <qt_windows.h>
|
# include <qt_windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_DARWIN)
|
||||||
|
# include "qcore_mac_p.h"
|
||||||
|
# if !defined(SHM_NAME_MAX)
|
||||||
|
// Based on PSEMNAMLEN in XNU's posix_sem.c, which would
|
||||||
|
// indicate the max length is 31, _excluding_ the zero
|
||||||
|
// terminator. But in practice (possibly due to an off-
|
||||||
|
// by-one bug in the kernel) the usable bytes are only 30.
|
||||||
|
# define SHM_NAME_MAX 30
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
#if !(defined(QT_NO_SHAREDMEMORY) && defined(QT_NO_SYSTEMSEMAPHORE))
|
#if !(defined(QT_NO_SHAREDMEMORY) && defined(QT_NO_SYSTEMSEMAPHORE))
|
||||||
@ -65,16 +76,32 @@ QSharedMemoryPrivate::makePlatformSafeKey(const QString &key,
|
|||||||
if (key.isEmpty())
|
if (key.isEmpty())
|
||||||
return QString();
|
return QString();
|
||||||
|
|
||||||
QString result = prefix;
|
QByteArray hex = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha1).toHex();
|
||||||
|
|
||||||
|
#if defined(Q_OS_DARWIN) && defined(QT_POSIX_IPC)
|
||||||
|
if (qt_apple_isSandboxed()) {
|
||||||
|
// Sandboxed applications on Apple platforms require the shared memory name
|
||||||
|
// to be in the form <application group identifier>/<custom identifier>.
|
||||||
|
// Since we don't know which application group identifier the user wants
|
||||||
|
// to apply, we instead document that requirement, and use the key directly.
|
||||||
|
return key;
|
||||||
|
} else {
|
||||||
|
// The shared memory name limit on Apple platforms is very low (30 characters),
|
||||||
|
// so we can't use the logic below of combining the prefix, key, and a hash,
|
||||||
|
// to ensure a unique and valid name. Instead we use the first part of the
|
||||||
|
// hash, which should still long enough to avoid collisions in practice.
|
||||||
|
return QLatin1Char('/') + hex.left(SHM_NAME_MAX - 1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString result = prefix;
|
||||||
for (QChar ch : key) {
|
for (QChar ch : key) {
|
||||||
if ((ch >= QLatin1Char('a') && ch <= QLatin1Char('z')) ||
|
if ((ch >= QLatin1Char('a') && ch <= QLatin1Char('z')) ||
|
||||||
(ch >= QLatin1Char('A') && ch <= QLatin1Char('Z')))
|
(ch >= QLatin1Char('A') && ch <= QLatin1Char('Z')))
|
||||||
result += ch;
|
result += ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray hex = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Sha1).toHex();
|
|
||||||
result.append(QLatin1String(hex));
|
result.append(QLatin1String(hex));
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
return result;
|
return result;
|
||||||
#elif defined(QT_POSIX_IPC)
|
#elif defined(QT_POSIX_IPC)
|
||||||
@ -121,6 +148,36 @@ QSharedMemoryPrivate::makePlatformSafeKey(const QString &key,
|
|||||||
process. This means that QSharedMemory should not be used across
|
process. This means that QSharedMemory should not be used across
|
||||||
multiple threads in the same process in HP-UX.
|
multiple threads in the same process in HP-UX.
|
||||||
|
|
||||||
|
\li Apple platforms: Sandboxed applications (including apps
|
||||||
|
shipped through the Apple App Store) require the use of POSIX
|
||||||
|
shared memory (instead of System V shared memory), which adds
|
||||||
|
a number of limitations, including:
|
||||||
|
|
||||||
|
\list
|
||||||
|
|
||||||
|
\li The key must be in the form \c {<application group identifier>/<custom identifier>},
|
||||||
|
as documented \l {https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24}
|
||||||
|
{here} and \l {https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups}
|
||||||
|
{here}.
|
||||||
|
|
||||||
|
\li The key length is limited to 30 characters.
|
||||||
|
|
||||||
|
\li On process exit, the named shared memory entries are not
|
||||||
|
cleaned up, so restarting the application and re-creating the
|
||||||
|
shared memory under the same name will fail. To work around this,
|
||||||
|
fall back to attaching to the existing shared memory entry:
|
||||||
|
|
||||||
|
\code
|
||||||
|
|
||||||
|
QSharedMemory shm("DEVTEAMID.app-group/shared");
|
||||||
|
if (!shm.create(42) && shm.error() == QSharedMemory::AlreadyExists)
|
||||||
|
shm.attach();
|
||||||
|
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
|
||||||
|
\endlist
|
||||||
|
|
||||||
\endlist
|
\endlist
|
||||||
|
|
||||||
Remember to lock the shared memory with lock() before reading from
|
Remember to lock the shared memory with lock() before reading from
|
||||||
|
@ -102,7 +102,6 @@ bool QSharedMemoryPrivate::create(qsizetype size)
|
|||||||
const int errorNumber = errno;
|
const int errorNumber = errno;
|
||||||
const QLatin1String function("QSharedMemory::attach (shm_open)");
|
const QLatin1String function("QSharedMemory::attach (shm_open)");
|
||||||
switch (errorNumber) {
|
switch (errorNumber) {
|
||||||
case ENAMETOOLONG:
|
|
||||||
case EINVAL:
|
case EINVAL:
|
||||||
errorString = QSharedMemory::tr("%1: bad name").arg(function);
|
errorString = QSharedMemory::tr("%1: bad name").arg(function);
|
||||||
error = QSharedMemory::KeyError;
|
error = QSharedMemory::KeyError;
|
||||||
@ -146,7 +145,6 @@ bool QSharedMemoryPrivate::attach(QSharedMemory::AccessMode mode)
|
|||||||
const int errorNumber = errno;
|
const int errorNumber = errno;
|
||||||
const QLatin1String function("QSharedMemory::attach (shm_open)");
|
const QLatin1String function("QSharedMemory::attach (shm_open)");
|
||||||
switch (errorNumber) {
|
switch (errorNumber) {
|
||||||
case ENAMETOOLONG:
|
|
||||||
case EINVAL:
|
case EINVAL:
|
||||||
errorString = QSharedMemory::tr("%1: bad name").arg(function);
|
errorString = QSharedMemory::tr("%1: bad name").arg(function);
|
||||||
error = QSharedMemory::KeyError;
|
error = QSharedMemory::KeyError;
|
||||||
|
@ -124,6 +124,13 @@ QT_BEGIN_NAMESPACE
|
|||||||
|
|
||||||
\endlist
|
\endlist
|
||||||
|
|
||||||
|
\b{Apple platforms:} Sandboxed applications (including apps
|
||||||
|
shipped through the Apple App Store) require the key to
|
||||||
|
be in the form \c {<application group identifier>/<custom identifier>},
|
||||||
|
as documented \l {https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24}
|
||||||
|
{here} and \l {https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups}
|
||||||
|
{here}, and the key length is limited to 30 characters.
|
||||||
|
|
||||||
\sa QSharedMemory, QSemaphore
|
\sa QSharedMemory, QSemaphore
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -54,6 +54,10 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
#if defined(Q_OS_DARWIN)
|
||||||
|
#include "qcore_mac_p.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "private/qcore_unix_p.h"
|
#include "private/qcore_unix_p.h"
|
||||||
|
|
||||||
// OpenBSD 4.2 doesn't define EIDRM, see BUGS section:
|
// OpenBSD 4.2 doesn't define EIDRM, see BUGS section:
|
||||||
@ -71,6 +75,16 @@ QT_BEGIN_NAMESPACE
|
|||||||
*/
|
*/
|
||||||
key_t QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode mode)
|
key_t QSystemSemaphorePrivate::handle(QSystemSemaphore::AccessMode mode)
|
||||||
{
|
{
|
||||||
|
#if defined(Q_OS_DARWIN)
|
||||||
|
if (qt_apple_isSandboxed()) {
|
||||||
|
errorString = QSystemSemaphore::tr("%1: System V semaphores are not available " \
|
||||||
|
"for sandboxed applications. Please build Qt with -feature-ipc_posix")
|
||||||
|
.arg(QLatin1String("QSystemSemaphore::handle:"));
|
||||||
|
error = QSystemSemaphore::PermissionDenied;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (key.isEmpty()){
|
if (key.isEmpty()){
|
||||||
errorString = QSystemSemaphore::tr("%1: key is empty")
|
errorString = QSystemSemaphore::tr("%1: key is empty")
|
||||||
.arg(QLatin1String("QSystemSemaphore::handle:"));
|
.arg(QLatin1String("QSystemSemaphore::handle:"));
|
||||||
|
@ -89,6 +89,12 @@ void QSystemSemaphorePrivate::setErrorString(const QString &function)
|
|||||||
errorString = QSystemSemaphore::tr("%1: out of resources").arg(function);
|
errorString = QSystemSemaphore::tr("%1: out of resources").arg(function);
|
||||||
error = QSystemSemaphore::OutOfResources;
|
error = QSystemSemaphore::OutOfResources;
|
||||||
break;
|
break;
|
||||||
|
#if defined(QT_POSIX_IPC)
|
||||||
|
case ENAMETOOLONG:
|
||||||
|
errorString = QSystemSemaphore::tr("%1: key too long").arg(function);
|
||||||
|
error = QSystemSemaphore::KeyError;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
errorString = QSystemSemaphore::tr("%1: unknown error %2").arg(function).arg(errno);
|
errorString = QSystemSemaphore::tr("%1: unknown error %2").arg(function).arg(errno);
|
||||||
error = QSystemSemaphore::UnknownError;
|
error = QSystemSemaphore::UnknownError;
|
||||||
|
@ -182,14 +182,15 @@ int tst_QSharedMemory::remove(const QString &key)
|
|||||||
if (key.isEmpty())
|
if (key.isEmpty())
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
// ftok requires that an actual file exists somewhere
|
|
||||||
QString fileName = QSharedMemoryPrivate::makePlatformSafeKey(key);
|
QString fileName = QSharedMemoryPrivate::makePlatformSafeKey(key);
|
||||||
|
|
||||||
|
#ifndef QT_POSIX_IPC
|
||||||
|
// ftok requires that an actual file exists somewhere
|
||||||
if (!QFile::exists(fileName)) {
|
if (!QFile::exists(fileName)) {
|
||||||
//qDebug() << "exits failed";
|
//qDebug() << "exits failed";
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef QT_POSIX_IPC
|
|
||||||
int unix_key = ftok(fileName.toLatin1().constData(), 'Q');
|
int unix_key = ftok(fileName.toLatin1().constData(), 'Q');
|
||||||
if (-1 == unix_key) {
|
if (-1 == unix_key) {
|
||||||
qDebug() << "ftok failed";
|
qDebug() << "ftok failed";
|
||||||
@ -209,9 +210,11 @@ int tst_QSharedMemory::remove(const QString &key)
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (shm_unlink(QFile::encodeName(fileName).constData()) == -1) {
|
if (shm_unlink(QFile::encodeName(fileName).constData()) == -1) {
|
||||||
|
if (errno != ENOENT) {
|
||||||
qDebug() << "shm_unlink failed";
|
qDebug() << "shm_unlink failed";
|
||||||
return -5;
|
return -5;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#endif // QT_POSIX_IPC
|
#endif // QT_POSIX_IPC
|
||||||
|
|
||||||
return QFile::remove(fileName);
|
return QFile::remove(fileName);
|
||||||
@ -374,7 +377,7 @@ void tst_QSharedMemory::lock()
|
|||||||
QVERIFY(!shm.lock());
|
QVERIFY(!shm.lock());
|
||||||
QCOMPARE(shm.error(), QSharedMemory::LockError);
|
QCOMPARE(shm.error(), QSharedMemory::LockError);
|
||||||
|
|
||||||
shm.setKey(QLatin1String("qsharedmemory"));
|
shm.setKey(rememberKey(QLatin1String("qsharedmemory")));
|
||||||
|
|
||||||
QVERIFY(!shm.lock());
|
QVERIFY(!shm.lock());
|
||||||
QCOMPARE(shm.error(), QSharedMemory::LockError);
|
QCOMPARE(shm.error(), QSharedMemory::LockError);
|
||||||
@ -411,6 +414,11 @@ void tst_QSharedMemory::removeWhileAttached()
|
|||||||
delete smOne;
|
delete smOne;
|
||||||
delete smTwo;
|
delete smTwo;
|
||||||
|
|
||||||
|
#ifdef QT_POSIX_IPC
|
||||||
|
// POSIX IPC doesn't guarantee that the shared memory is removed
|
||||||
|
remove("one");
|
||||||
|
#endif
|
||||||
|
|
||||||
// three shouldn't be able to attach
|
// three shouldn't be able to attach
|
||||||
QSharedMemory smThree(QLatin1String("one"));
|
QSharedMemory smThree(QLatin1String("one"));
|
||||||
QVERIFY(!smThree.attach());
|
QVERIFY(!smThree.attach());
|
||||||
@ -580,6 +588,10 @@ void tst_QSharedMemory::simpleDoubleProducerConsumer()
|
|||||||
int size = 512;
|
int size = 512;
|
||||||
QVERIFY(producer.create(size));
|
QVERIFY(producer.create(size));
|
||||||
QVERIFY(producer.detach());
|
QVERIFY(producer.detach());
|
||||||
|
#ifdef QT_POSIX_IPC
|
||||||
|
// POSIX IPC doesn't guarantee that the shared memory is removed
|
||||||
|
remove("market");
|
||||||
|
#endif
|
||||||
QVERIFY(producer.create(size));
|
QVERIFY(producer.create(size));
|
||||||
|
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user