Skip to content

Instantly share code, notes, and snippets.

@JJTech0130
Last active May 5, 2025 20:48
Show Gist options
  • Save JJTech0130/07e2458df592faad1d2ba72283a0ca50 to your computer and use it in GitHub Desktop.
Save JJTech0130/07e2458df592faad1d2ba72283a0ca50 to your computer and use it in GitHub Desktop.
/*
clang ./locks.m -framework Foundation -l sqlite3 && ./a.out
*/
#import <Foundation/Foundation.h>
#include <unistd.h>
#include <libproc.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <fcntl.h>
#include <spawn.h>
extern int _sqlite3_lockstate(const char *path, int pid);
/* Reimplementation of the algorithm used by runningboardd from decompilation */
@interface RBProcess : NSObject
- (instancetype)initWithPid:(int)pid;
- (NSMutableSet *)_lock_lockedFilePathsIgnoring: (NSMutableSet *)ignoring;
@end
@implementation RBProcess
int _pid;
- (instancetype)initWithPid:(int)pid {
self = [super init];
if (self) {
_pid = pid;
}
return self;
}
- (NSMutableSet *)_lock_lockedFilePathsIgnoring: (NSMutableSet *)ignoring {
int pid = _pid;
int pidinfo_size = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0);
if (pidinfo_size <= 0) {
// _rbs_process_log with strerr
return nil;
}
void *pidinfo = malloc(pidinfo_size);
pidinfo_size = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, pidinfo, pidinfo_size);
NSMutableSet *openFilePaths = [NSMutableSet set];
if (pidinfo_size >= 8) {
uint64_t count = pidinfo_size / sizeof(struct proc_fdinfo);
struct proc_fdinfo *fdinfo = (struct proc_fdinfo *)pidinfo;
while (count--) {
if (fdinfo->proc_fdtype == PROX_FDTYPE_VNODE) {
struct vnode_fdinfowithpath vnodeinfo;
//memset(&vnodeinfo, 0, 0x200); // TODO: Why not sizeof(vnodeinfo)?
int vnodeinfo_size = proc_pidfdinfo(pid, fdinfo->proc_fd, PROC_PIDFDVNODEPATHINFO, &vnodeinfo, sizeof(vnodeinfo));
if (vnodeinfo_size == 0) {
// _rbs_process_log with %{public}@ proc_pidfdinfo failed for fd %d with errno %d
continue;
} else if (vnodeinfo_size < sizeof(vnodeinfo)) {
// _rbs_process_log with %{public}@ Weird size (%d != %lu) for fd %d
continue;
}
int64_t pathlen = strlen(vnodeinfo.pvip.vip_path);
if (pathlen == 0) {
// _rbs_process_log with%{public}@ nodeFDInfo.pvip.vip_path is empty for one fd
continue;
}
NSString *path = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:vnodeinfo.pvip.vip_path length:pathlen];
if (path == nil) {
continue;
}
path = [path stringByStandardizingPath];
[openFilePaths addObject:path];
}
fdinfo++;
}
}
NSMutableSet *lockedFilePaths = [NSMutableSet set];
for (NSString *path in openFilePaths) {
char *path_c = (char *)[path UTF8String];
struct stat statbuf;
if (stat(path_c, &statbuf) != 0) {
// _rbs_process_log with %{public}@ Could not stat %{public}@: %{public}s
NSLog(@"Could not stat %@: %s", path, strerror(errno));
continue;
}
if ((statbuf.st_mode & S_IFMT) != S_IFREG) {
// _rbs_process_log with %{public}@ Not checking lock on special file: %{public}@
NSLog(@"Not checking lock on special file: %@", path);
continue;
}
for (NSString *ignoringPath in ignoring) {
if ([path hasPrefix:ignoringPath]) {
// _rbs_process_log with %{public}@: Ignoring file %{public}@ because it is in an allowed path: %{public}@
NSLog(@"Ignoring file %@ because it is in an allowed path: %@", path, ignoringPath);
continue;
}
}
if ([path hasSuffix:@"-shm"] || [path hasSuffix:@"-wal"] || [path hasSuffix:@"-journal"]) {
// _rbs_process_log with %{public}@ Ignoring SQLite journal file: %{public}@
NSLog(@"Ignoring SQLite journal file: %@", path);
continue;
}
if (getxattr(path_c, "com.apple.runningboard.can-suspend", NULL, 0, 0, 0) == 1) {
char value;
getxattr(path_c, "com.apple.runningboard.can-suspend", &value, sizeof(value), 0, 0);
if (value != 0) {
// _rbs_process_log with %{public}@ Ignoring file with can-suspend-locked: %{public}@
NSLog(@"Ignoring file with can-suspend-locked: %@", path);
continue;
}
}
int sqlite_lock = _sqlite3_lockstate(path_c, pid);
if (sqlite_lock == 0) {
// _rbs_process_log with %{public}@ Ignoring unlocked SQLite database: %{public}@
NSLog(@"Ignoring unlocked SQLite database: %@", path);
continue;
}
if (sqlite_lock == 1) {
// _rbs_process_log with %{public}@ Found locked SQLite database: %{public}@
NSLog(@"Found locked SQLite database: %@", path);
[lockedFilePaths addObject:path];
} else {
int fd = open(path_c, O_RDONLY | O_NOCTTY);
if (fd <= 1) {
continue;
}
struct flock fl;
memset(&fl, 0, sizeof(fl));
fl.l_type = F_WRLCK;
fl.l_pid = pid;
int lock = fcntl(fd, F_GETLKPID, &fl);
if (lock == -1) {
continue;
}
if ((fl.l_type &~ F_UNLCK) == 1) {
// _rbs_process_log with %{public}@ Found locked file lock: %{public}@
NSLog(@"Found locked file lock: %@", path);
[lockedFilePaths addObject:path];
}
}
}
return lockedFilePaths;
}
@end
/* Test the bug */
void child() {
// Lock temporary file using flock
int fd = open("/tmp/test.lock", O_RDWR | O_CREAT, 0666);
if (fd < 0) {
perror("open failed");
exit(1);
}
struct flock fl;
memset(&fl, 0, sizeof(fl));
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0; // Lock the whole file
//fl.l_pid = getpid();
if (fcntl(fd, F_SETLK, &fl) < 0) {
perror("fcntl failed");
close(fd);
exit(1);
}
printf("Process %d: locked file /tmp/test.lock using F_SETLK\n", getpid());
// Wait for a while to keep the lock
sleep(2);
// Unlock with F_UNLCK
fl.l_type = F_UNLCK;
if (fcntl(fd, F_SETLK, &fl) < 0) {
perror("fcntl unlock failed");
close(fd);
exit(1);
}
printf("Process %d: unlocked file /tmp/test.lock using F_UNLCK\n", getpid());
// Close and reopen the file
close(fd);
fd = open("/tmp/test.lock", O_RDWR | O_CREAT, 0666);
// Lock again using flock() instead of fcntl
flock(fd, LOCK_EX);
printf("Process %d: locked file /tmp/test.lock using flock\n", getpid());
// Wait for a while to keep the lock
sleep(2);
printf("Process %d: exiting\n", getpid());
}
void check_locks() {
int fd = open("/tmp/test.lock", O_RDWR);
if (fd < 0) {
perror("open failed");
exit(1);
}
// Check if we have a lock according to the algorithm
@autoreleasepool {
RBProcess *process = [[RBProcess alloc] initWithPid:getpid()];
NSMutableSet *lockedFilePaths = [process _lock_lockedFilePathsIgnoring:[NSMutableSet set]];
if (lockedFilePaths.count > 0) {
for (NSString *path in lockedFilePaths) {
printf("Process %d: found locked file: %s\n", getpid(), [path UTF8String]);
}
} else {
printf("Process %d: no locked files found\n", getpid());
}
}
}
void parent() {
// Wait for the child to lock the file
sleep(1);
// Open lock file
check_locks();
sleep(2);
check_locks();
sleep(2);
check_locks();
}
extern char **environ;
int main(int argc, char *argv[]) {
printf("Process %d: started\n", getpid());
if (argc == 2 && strcmp(argv[1], "child") == 0) {
child();
return 0;
}
pid_t pid;
char *child_args[] = {argv[0], "child", NULL};
int status = posix_spawn(&pid, argv[0], NULL, NULL, child_args, environ);
if (status != 0) {
perror("posix_spawn failed");
return 1;
}
parent();
waitpid(pid, &status, 0);
printf("Process %d: child exited with status %d\n", getpid(), WEXITSTATUS(status));
return 0;
}
Process 73339: started
Process 73340: started
Process 73340: locked file /tmp/test.lock using F_SETLK
Process 73339: no locked files found
Process 73340: unlocked file /tmp/test.lock using F_UNLCK
Process 73340: locked file /tmp/test.lock using flock
**Process 73339: found locked file: /tmp/test.lock**
Process 73340: exiting
Process 73339: no locked files found
Process 73339: child exited with status 0 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment