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"