Hi, how could I get the command line arguments of a process given its audit token.
My app is a Content Filter Network Extension written in swift. I can obtain the audit token from NEFilterFlow but I can't figure out how to get the process arguments, I was able to get the pid from the audit token using audit_token_to_pid.
This works fine, the only thing I have yet to see is if it has any memory leaks.
Your call to free is both necessary and sufficient, but I found it a bit weird because I usually pair allocators. That is, if I allocate with malloc I free with free but if I allocate with UnsafeMutablePointer I then free with its deallocate() method.
If you have any suggestions to the func please let me know.
At a the ‘API’ level, it’s weird that you return NSString rather than String.
As far as the implementation is concerned, it’s full of unsafe pointer manipulation that’s… well… very unsafe. When I’m parsing untrusted data — and remember that this is untrusted, in that the remote process can modify the data in any way — I prefer to build a parser that’s more paranoid.
To start, I’d split the code for getting the process’s argument memory block off from the code that parses it. This has a number of benefits:
-
It let’s me test the parsing code in isolation.
-
If the technique for getting the memory block changes, I can adapt to that without changing the parser.
-
It isolates the unsafe code.
So, here’s my code for getting the memory block:
func argumentData(for pid: pid_t) throws -> Data {
// There should be a better way to get a process’s arguments
// (FB9149624) but right now you have to use `KERN_PROCARGS2`
// and then parse the results.
var argMax: CInt = 0
var argMaxSize = size_t(MemoryLayout.size(ofValue: argMax))
let err = sysctlbyname("kern.argmax", &argMax, &argMaxSize, nil, 0)
guard err >= 0 else {
throw System.Errno(rawValue: errno)
}
precondition(argMaxSize != 0)
var result = Data(count: Int(argMax))
let resultSize = try result.withUnsafeMutableBytes { buf -> Int in
var mib: [CInt] = [
CTL_KERN,
KERN_PROCARGS2,
pid
]
var bufSize = buf.count
let err = sysctl(&mib, CUnsignedInt(mib.count), buf.baseAddress!, &bufSize, nil, 0)
guard err >= 0 else {
throw System.Errno(rawValue: errno)
}
return bufSize
}
result = result.prefix(resultSize)
return result
}
Note that the only unsafe code here is the code that has to be unsafe because I’m calling sysctlbyname and sysctl.
And here’s my code for parsing:
func argumentsFromArgumentData(_ data: Data) throws -> [String] {
// The algorithm here was was ‘stolen’ from the Darwin source for `ps`.
//
// <https://opensource.apple.com/source/adv_cmds/adv_cmds-176/ps/print.c.auto.html>
// Parse `argc`. We’re assuming the value is little endian here, which is
// currently accurate but it could be a problem if we’ve “gone back to
// metric”.
var remaining = data[...]
guard remaining.count >= 6 else {
throw ParseError.unexpectedEnd
}
let count32 = remaining.prefix(4).reversed().reduce(0, { $0 << 8 | UInt32($1) })
remaining = remaining.dropFirst(4)
// Skip the saved executable path.
remaining = remaining.drop(while: { $0 != 0 })
remaining = remaining.drop(while: { $0 == 0 })
// Now parse `argv[0]` through `argv[argc - 1]`.
var result: [String] = []
for _ in 0..<count32 {
let argBytes = remaining.prefix(while: { $0 != 0 })
guard let arg = String(bytes: argBytes, encoding: .utf8) else {
throw ParseError.argumentIsNotUTF8
}
result.append(arg)
remaining = remaining.dropFirst(argBytes.count)
guard remaining.count != 0 else {
throw ParseError.unexpectedEnd
}
remaining = remaining.dropFirst()
}
return result
}
enum ParseError: Error {
case unexpectedEnd
case argumentIsNotUTF8
}
There’s nothing unsafe here, so the worst it can do is return the wrong results. Which is always going to be a risk anyway because this data is under the control of the target process.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"