为什么在设备 compiler/toolchain 上使用 iOS 时,各种项目的构建会因“不允许操作”而失败?
Why do builds for various projects fail with ‘Operation not permitted’ using iOS on-device compiler/toolchain?
我是一名中等水平的 Linux/Unix 用户,试图在(越狱)iPad 上为 iPad 编译软件。
许多构建(例如,make 和 tex-live)因一些 Operation not permitted
错误而失败。这看起来像 Can't exec "blah": Operation not permitted
或 execvp: blah: Operation not permitted
,其中 blah
是 aclocal
、configure
脚本、libtool
或任何东西。奇怪的是,在 Makefile
或 configure
脚本中找到有问题的行并在其前面加上 sudo -u mobile -E
将解决该行的错误,只是为了它会在后面的行或另一行中重新出现文件。由于我 运行 构建脚本为 mobile
,我不明白这怎么可能解决问题,但确实如此。我已经确认进行这些更改实际上允许脚本成功运行到那时。 运行 带有 sudo
或 sudo -u mobile -E
and/or 运行 整个构建的构建脚本 root
没有解决问题;无论是哪种,我仍然必须编辑构建脚本以添加 sudo
。
我想知道为什么会这样,如果可能的话,我想知道如何在不编辑构建脚本的情况下解决这个问题。关于这些类型的错误的任何信息对我来说都是有趣的,即使它们不能解决我的问题。我知道 permissions/security/entitlements 系统在 iOS 上很不寻常,我想了解更多关于它是如何工作的。
我在越狱的 iOS 13.5 上使用 iPad Pro 4,使用来自 sbingner 和 MCApollo 的 repos 的构建工具(repo.bingner.com 和 mcapollo.github。io/Public).特别是,我使用的是 LLVM 5(从 sbingner 的旧 deb 手动安装)、Clang 10、Darwin CC 工具 927 和 GNU Make 4.2.1 的构建。我已将 CC
、CXX
、CFLAGS
等设置为指向 clang-10
和我的 iOS 13.5 SDK 和 -isysroot
并已确认这些设置正在工作。我想用更新的版本替换它们,但由于这个问题和其他一些问题,我还不能为自己构建这些工具。如有必要,我确实可以访问 Mac 进行交叉编译,但我宁愿只使用我的 iPad,因为我喜欢挑战。
我可以附上任何必要的日志或提供更多有用的信息;我对这个问题了解不够,不知道哪些信息有用。在此先感谢您对我的帮助!
对于最终需要在没有解决此问题的越狱时解决此问题的任何人,我已经编写(粘贴在下面)一个基于来自源的 posix_spawn
实现的用户态挂钩Apple 的 xnu 内核。
使用 Theos 编译它,并通过将环境变量 DYLD_INSERT_LIBRARIES
设置为生成的 dylib 的路径,将其注入到您的 shell 生成的所有进程中。注意:一些调整注入器(即 libhooker,参见 here)重置 DYLD_INSERT_LIBRARIES
,因此如果您注意到此行为,请务必仅注入您的库。
因为 iOS 调用 posix_spawn
中的 exec 系统调用的实现,此挂钩修复了我 运行 迄今为止遇到的所有与 exec 相关的问题。
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <spawn.h>
// Copied from bsd/kern/kern_exec.c
#define IS_WHITESPACE(ch) ((ch == ' ') || (ch == '\t'))
#define IS_EOL(ch) ((ch == '#') || (ch == '\n'))
// Copied from bsd/sys/imgact.h
#define IMG_SHSIZE 512
// Here, we provide an alternate implementation of posix_spawn which correctly handles #!.
// This is based on the implementation of posix_spawn in bsd/kern/kern_exec.c from Apple's xnu source.
// Thus, I am fairly confident that this posix_spawn has correct behavior relative to macOS.
%hookf(int, posix_spawn, pid_t *pid, const char *orig_path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const orig_argv[], char *const envp[]) {
// Call orig before checking for anything.
// This mirrors the standard implementation of posix_spawn because it first checks if we are spawning a binary.
int err = %orig;
// %orig returns EPERM when spawning a script.
// Thus, if err is anything other than EPERM, we can just return like normal.
if (err != EPERM)
return err;
// At this point, we do not need to check for exec permissions or anything like that.
// because posix_spawn would have returned that error instead of EPERM.
// Now we open the file for reading so that we can check if it's a script.
// If it turns out not to be a script, the EPERM must be from something else
// so we just return err.
FILE *file = fopen(orig_path, "r");
if (file == NULL) {
return err;
}
if (fseek(file, 0, SEEK_SET)) {
return err;
}
// In exec_activate_image, the data buffer is filled with the first PAGE_SIZE bytes of the file.
// However, in exec_shell_imgact, only the first IMG_SHSIZE bytes are used.
// Thus, we read IMG_SHSIZE bytes out of our file.
// The buffer is filled with newlines so that if the file is not IMG_SHSIZE bytes,
// the logic reads an IS_EOL.
char vdata[IMG_SHSIZE] = {'\n'};
if (fread(vdata, 1, IMG_SHSIZE, file) < 2) { // If we couldn't read at least two bytes, it's not a script.
fclose(file);
return err;
}
// Now that we've filled the buffer, we don't need the file anymore.
fclose(file);
// Now we follow exec_shell_imgact.
// The point of this is to confirm we have a script
// and extract the usable part of the interpreter+arg string.
// Where they return -1, we don't have a shell script, so we return err.
// Where they return an error, we return that same error.
// We don't bother doing any SUID stuff because SUID scripts should be disabled anyway.
char *ihp;
char *line_startp, *line_endp;
// Make sure we have a shell script.
if (vdata[0] != '#' || vdata[1] != '!') {
return err;
}
// Try to find the first non-whitespace character
for (ihp = &vdata[2]; ihp < &vdata[IMG_SHSIZE]; ihp++) {
if (IS_EOL(*ihp)) {
// Did not find interpreter, "#!\n"
return ENOEXEC;
} else if (IS_WHITESPACE(*ihp)) {
// Whitespace, like "#! /bin/sh\n", keep going.
} else {
// Found start of interpreter
break;
}
}
if (ihp == &vdata[IMG_SHSIZE]) {
// All whitespace, like "#! "
return ENOEXEC;
}
line_startp = ihp;
// Try to find the end of the interpreter+args string
for (; ihp < &vdata[IMG_SHSIZE]; ihp++) {
if (IS_EOL(*ihp)) {
// Got it
break;
} else {
// Still part of interpreter or args
}
}
if (ihp == &vdata[IMG_SHSIZE]) {
// A long line, like "#! blah blah blah" without end
return ENOEXEC;
}
// Backtrack until we find the last non-whitespace
while (IS_EOL(*ihp) || IS_WHITESPACE(*ihp)) {
ihp--;
}
// The character after the last non-whitespace is our logical end of line
line_endp = ihp + 1;
/*
* Now we have pointers to the usable part of:
*
* "#! /usr/bin/int first second third \n"
* ^ line_startp ^ line_endp
*/
// Now, exec_shell_imgact copies the interpreter into another buffer and then null-terminates it.
// Then, it copies the entire interpreter+args into another buffer and null-terminates it for later processing into argv.
// This processing is done in exec_extract_strings, which goes through and null-terminates each argument.
// We will just do this all at once since that's much easier.
// Keep track of how many arguments we have.
int i_argc = 0;
ihp = line_startp;
while (true) {
// ihp is on the start of an argument.
i_argc++;
// Scan to the end of the argument.
for (; ihp < line_endp; ihp++) {
if (IS_WHITESPACE(*ihp)) {
// Found the end of the argument
break;
} else {
// Keep going
}
}
// Null terminate the argument
*ihp = '[=10=]';
// Scan to the beginning of the next argument.
for (; ihp < line_endp; ihp++) {
if (!IS_WHITESPACE(*ihp)) {
// Found the next argument
break;
} else {
// Keep going
}
}
if (ihp == line_endp) {
// We've reached the end of the arg string
break;
}
// If we are here, ihp is the start of an argument.
}
// Now line_startp is a bunch of null-terminated arguments possibly padded by whitespace.
// i_argc is now the count of the interpreter arguments.
// Our new argv should look like i_argv[0], i_argv[1], i_argv[2], ..., orig_path, orig_argv[1], orig_argv[2], ..., NULL
// where i_argv is the arguments to be extracted from line_startp;
// To allocate our new argv, we need to know orig_argc.
int orig_argc = 0;
while (orig_argv[orig_argc] != NULL) {
orig_argc++;
}
// We need space for i_argc + 1 + (orig_argc - 1) + 1 char*'s
char *argv[i_argc + orig_argc + 1];
// Copy i_argv into argv
int i = 0;
ihp = line_startp;
for (; i < i_argc; i++) {
// ihp is on the start of an argument
argv[i] = ihp;
// Scan to the next null-terminator
for (; ihp < line_endp; ihp++) {
if (*ihp == '[=10=]') {
// Found it
break;
} else {
// Keep going
}
}
// Go to the next character
ihp++;
// Then scan to the next argument.
// There must be another argument because we already counted i_argc.
for (; ihp < line_endp; ihp++) {
if (!IS_WHITESPACE(*ihp)) {
// Found it
break;
} else {
// Keep going
}
}
// ihp is on the start of an argument.
}
// Then, copy orig_path into into argv.
// We need to make a copy of orig_path to avoid issues with const.
char orig_path_copy[strlen(orig_path)+1];
strcpy(orig_path_copy, orig_path);
argv[i] = orig_path_copy;
i++;
// Now, copy orig_argv[1...] into argv.
for (int j = 1; j < orig_argc; i++, j++) {
argv[i] = orig_argv[j];
}
// Finally, add the null.
argv[i] = NULL;
// Now, our argv is setup correctly.
// Now, we can call out to posix_spawn again.
// The interpeter is in argv[0], so we use that for the path.
return %orig(pid, argv[0], file_actions, attrp, argv, envp);
}
我是一名中等水平的 Linux/Unix 用户,试图在(越狱)iPad 上为 iPad 编译软件。
许多构建(例如,make 和 tex-live)因一些 Operation not permitted
错误而失败。这看起来像 Can't exec "blah": Operation not permitted
或 execvp: blah: Operation not permitted
,其中 blah
是 aclocal
、configure
脚本、libtool
或任何东西。奇怪的是,在 Makefile
或 configure
脚本中找到有问题的行并在其前面加上 sudo -u mobile -E
将解决该行的错误,只是为了它会在后面的行或另一行中重新出现文件。由于我 运行 构建脚本为 mobile
,我不明白这怎么可能解决问题,但确实如此。我已经确认进行这些更改实际上允许脚本成功运行到那时。 运行 带有 sudo
或 sudo -u mobile -E
and/or 运行 整个构建的构建脚本 root
没有解决问题;无论是哪种,我仍然必须编辑构建脚本以添加 sudo
。
我想知道为什么会这样,如果可能的话,我想知道如何在不编辑构建脚本的情况下解决这个问题。关于这些类型的错误的任何信息对我来说都是有趣的,即使它们不能解决我的问题。我知道 permissions/security/entitlements 系统在 iOS 上很不寻常,我想了解更多关于它是如何工作的。
我在越狱的 iOS 13.5 上使用 iPad Pro 4,使用来自 sbingner 和 MCApollo 的 repos 的构建工具(repo.bingner.com 和 mcapollo.github。io/Public).特别是,我使用的是 LLVM 5(从 sbingner 的旧 deb 手动安装)、Clang 10、Darwin CC 工具 927 和 GNU Make 4.2.1 的构建。我已将 CC
、CXX
、CFLAGS
等设置为指向 clang-10
和我的 iOS 13.5 SDK 和 -isysroot
并已确认这些设置正在工作。我想用更新的版本替换它们,但由于这个问题和其他一些问题,我还不能为自己构建这些工具。如有必要,我确实可以访问 Mac 进行交叉编译,但我宁愿只使用我的 iPad,因为我喜欢挑战。
我可以附上任何必要的日志或提供更多有用的信息;我对这个问题了解不够,不知道哪些信息有用。在此先感谢您对我的帮助!
对于最终需要在没有解决此问题的越狱时解决此问题的任何人,我已经编写(粘贴在下面)一个基于来自源的 posix_spawn
实现的用户态挂钩Apple 的 xnu 内核。
使用 Theos 编译它,并通过将环境变量 DYLD_INSERT_LIBRARIES
设置为生成的 dylib 的路径,将其注入到您的 shell 生成的所有进程中。注意:一些调整注入器(即 libhooker,参见 here)重置 DYLD_INSERT_LIBRARIES
,因此如果您注意到此行为,请务必仅注入您的库。
因为 iOS 调用 posix_spawn
中的 exec 系统调用的实现,此挂钩修复了我 运行 迄今为止遇到的所有与 exec 相关的问题。
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <spawn.h>
// Copied from bsd/kern/kern_exec.c
#define IS_WHITESPACE(ch) ((ch == ' ') || (ch == '\t'))
#define IS_EOL(ch) ((ch == '#') || (ch == '\n'))
// Copied from bsd/sys/imgact.h
#define IMG_SHSIZE 512
// Here, we provide an alternate implementation of posix_spawn which correctly handles #!.
// This is based on the implementation of posix_spawn in bsd/kern/kern_exec.c from Apple's xnu source.
// Thus, I am fairly confident that this posix_spawn has correct behavior relative to macOS.
%hookf(int, posix_spawn, pid_t *pid, const char *orig_path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const orig_argv[], char *const envp[]) {
// Call orig before checking for anything.
// This mirrors the standard implementation of posix_spawn because it first checks if we are spawning a binary.
int err = %orig;
// %orig returns EPERM when spawning a script.
// Thus, if err is anything other than EPERM, we can just return like normal.
if (err != EPERM)
return err;
// At this point, we do not need to check for exec permissions or anything like that.
// because posix_spawn would have returned that error instead of EPERM.
// Now we open the file for reading so that we can check if it's a script.
// If it turns out not to be a script, the EPERM must be from something else
// so we just return err.
FILE *file = fopen(orig_path, "r");
if (file == NULL) {
return err;
}
if (fseek(file, 0, SEEK_SET)) {
return err;
}
// In exec_activate_image, the data buffer is filled with the first PAGE_SIZE bytes of the file.
// However, in exec_shell_imgact, only the first IMG_SHSIZE bytes are used.
// Thus, we read IMG_SHSIZE bytes out of our file.
// The buffer is filled with newlines so that if the file is not IMG_SHSIZE bytes,
// the logic reads an IS_EOL.
char vdata[IMG_SHSIZE] = {'\n'};
if (fread(vdata, 1, IMG_SHSIZE, file) < 2) { // If we couldn't read at least two bytes, it's not a script.
fclose(file);
return err;
}
// Now that we've filled the buffer, we don't need the file anymore.
fclose(file);
// Now we follow exec_shell_imgact.
// The point of this is to confirm we have a script
// and extract the usable part of the interpreter+arg string.
// Where they return -1, we don't have a shell script, so we return err.
// Where they return an error, we return that same error.
// We don't bother doing any SUID stuff because SUID scripts should be disabled anyway.
char *ihp;
char *line_startp, *line_endp;
// Make sure we have a shell script.
if (vdata[0] != '#' || vdata[1] != '!') {
return err;
}
// Try to find the first non-whitespace character
for (ihp = &vdata[2]; ihp < &vdata[IMG_SHSIZE]; ihp++) {
if (IS_EOL(*ihp)) {
// Did not find interpreter, "#!\n"
return ENOEXEC;
} else if (IS_WHITESPACE(*ihp)) {
// Whitespace, like "#! /bin/sh\n", keep going.
} else {
// Found start of interpreter
break;
}
}
if (ihp == &vdata[IMG_SHSIZE]) {
// All whitespace, like "#! "
return ENOEXEC;
}
line_startp = ihp;
// Try to find the end of the interpreter+args string
for (; ihp < &vdata[IMG_SHSIZE]; ihp++) {
if (IS_EOL(*ihp)) {
// Got it
break;
} else {
// Still part of interpreter or args
}
}
if (ihp == &vdata[IMG_SHSIZE]) {
// A long line, like "#! blah blah blah" without end
return ENOEXEC;
}
// Backtrack until we find the last non-whitespace
while (IS_EOL(*ihp) || IS_WHITESPACE(*ihp)) {
ihp--;
}
// The character after the last non-whitespace is our logical end of line
line_endp = ihp + 1;
/*
* Now we have pointers to the usable part of:
*
* "#! /usr/bin/int first second third \n"
* ^ line_startp ^ line_endp
*/
// Now, exec_shell_imgact copies the interpreter into another buffer and then null-terminates it.
// Then, it copies the entire interpreter+args into another buffer and null-terminates it for later processing into argv.
// This processing is done in exec_extract_strings, which goes through and null-terminates each argument.
// We will just do this all at once since that's much easier.
// Keep track of how many arguments we have.
int i_argc = 0;
ihp = line_startp;
while (true) {
// ihp is on the start of an argument.
i_argc++;
// Scan to the end of the argument.
for (; ihp < line_endp; ihp++) {
if (IS_WHITESPACE(*ihp)) {
// Found the end of the argument
break;
} else {
// Keep going
}
}
// Null terminate the argument
*ihp = '[=10=]';
// Scan to the beginning of the next argument.
for (; ihp < line_endp; ihp++) {
if (!IS_WHITESPACE(*ihp)) {
// Found the next argument
break;
} else {
// Keep going
}
}
if (ihp == line_endp) {
// We've reached the end of the arg string
break;
}
// If we are here, ihp is the start of an argument.
}
// Now line_startp is a bunch of null-terminated arguments possibly padded by whitespace.
// i_argc is now the count of the interpreter arguments.
// Our new argv should look like i_argv[0], i_argv[1], i_argv[2], ..., orig_path, orig_argv[1], orig_argv[2], ..., NULL
// where i_argv is the arguments to be extracted from line_startp;
// To allocate our new argv, we need to know orig_argc.
int orig_argc = 0;
while (orig_argv[orig_argc] != NULL) {
orig_argc++;
}
// We need space for i_argc + 1 + (orig_argc - 1) + 1 char*'s
char *argv[i_argc + orig_argc + 1];
// Copy i_argv into argv
int i = 0;
ihp = line_startp;
for (; i < i_argc; i++) {
// ihp is on the start of an argument
argv[i] = ihp;
// Scan to the next null-terminator
for (; ihp < line_endp; ihp++) {
if (*ihp == '[=10=]') {
// Found it
break;
} else {
// Keep going
}
}
// Go to the next character
ihp++;
// Then scan to the next argument.
// There must be another argument because we already counted i_argc.
for (; ihp < line_endp; ihp++) {
if (!IS_WHITESPACE(*ihp)) {
// Found it
break;
} else {
// Keep going
}
}
// ihp is on the start of an argument.
}
// Then, copy orig_path into into argv.
// We need to make a copy of orig_path to avoid issues with const.
char orig_path_copy[strlen(orig_path)+1];
strcpy(orig_path_copy, orig_path);
argv[i] = orig_path_copy;
i++;
// Now, copy orig_argv[1...] into argv.
for (int j = 1; j < orig_argc; i++, j++) {
argv[i] = orig_argv[j];
}
// Finally, add the null.
argv[i] = NULL;
// Now, our argv is setup correctly.
// Now, we can call out to posix_spawn again.
// The interpeter is in argv[0], so we use that for the path.
return %orig(pid, argv[0], file_actions, attrp, argv, envp);
}