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
Last active
May 5, 2025 20:48
-
-
Save JJTech0130/07e2458df592faad1d2ba72283a0ca50 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment