Get Process ID (pid) from NEFilterFlow & sourceAppAuditToken

Dear all,


In macOS Catalina we have the new NetworkExtension framework that can filter network trafic on a computer.

In my usecase I need the PID of the process that is the originator of the network flow. I'm aware that PID are not a reliable way to identify a process (since PIDs can be reused), but in my usecase only PID can identify what I need.


In handleNewFlow(_ flow: NEFilterFlow) we can get the sourceAppAuditToken (flow.sourceAppAuditToken), where sourceAppAuditToken is a Data type. Is there a way to convert this sourceAppAuditToken to a PID value?


I'm also aware of getting the signature of the process (eventually the Bundle ID) with SecCodeCopySigningInformation / kSecCSDynamicInformation, but again in my usecase it does not help.


A way to do this is to call "netstat" and look for the local port in the output and get the PID from there, but sometimes this is not very reliable.

Any ideas how to do this?


Regards,

Alex

Answered by DTS Engineer in 807971022

I wanted to suggest an alternative approach here, namely to add an initialiser to audit_token_t:

extension audit_token_t {

    init?(data: Data) {
        guard data.count == MemoryLayout<audit_token_t>.size else { return nil }
        self = data.withUnsafeBytes { buf in
            buf.baseAddress!.load(as: audit_token_t.self)
        }
    }
}

These days I prefer this option because I can use it in more circumstances. For example, NEFilterFlow now has a second audit token property, sourceProcessAuditToken, and this initialiser makes it easy to work with either.

Oh, and this code uses load(fromByteOffset:as:), which I find to be very convenient.

But, who knows, maybe in another four years I’ll have changed my mind again and present yet another new option (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

I think

audit_token_to_pid
, from
<bsm/libbsm.h>
, is the droid you’re looking for.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hello,


The "audit_token_to_pid" function accepts a "audit_token_t" parameter.

How can I convert the "flow.sourceAppAuditToken" (which is a Data type) to the required "audit_token_t" type?

Any hints?


Thank you

Ohh, found the solution. It's quite easy. Basically you have to convert the "flow.sourceAppAuditToken" to an array of UInt32. This array will have 8 elements. With that array you can init a audit_token_t.


let array: [UInt32] = convertToArray(flow.sourceAppAuditToken)

let audit_token = audit_token_t(val: (array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7]))

let pid = audit_token_to_pid(token_t)


Hope this helps to someone.

Regards

Hi Quinn,


Thanks for the helpful info here. We also need the PID from the NEFilterFlow.


Unfortuantely the above solution is giving us problems:

audit_token_to_pid


Here is our full code using Swift.

extension NEFilterFlow
{
    func pid() -> pid_t
    {
        let array8 = Array( self.sourceAppAuditToken! )
        let array32 = array8.map { UInt32($0) }
        let audit_token = audit_token_t(val: (array32[0], array32[1], array32[2], array32[3], array32[4], array32[5], array32[6], array32[7]))
        return audit_token_to_pid( audit_token )
    }
}

I'm wondering if our conversion from Data to audit_token_t is the culprit.


The resulting PID is a 0 or 1, regardless of input.


Anything stand out?


Thanks!

I have the same issue with 10.15.4

Moreover sourceAppAuditToken array has the same set of values regardless of the process.

I have a question since I know almost nothing about Swift. When you create the array8 buffer by passing socketFlow.sourceAppAuditToken! into Array, is it passing the data bytes from the socketFlow.sourceAppAuditToken NSData object or the NSData object itself? I'm not sure how all this "unwraps" in Swift or exactly how you'd tell it to use the data bytes.


I'm learning Swift as I go and was just wondering how it processes things like that.


Thanks

Sorry I didn’t follow-up here earlier. I’m not entirely sure why I missed this.

I reviewed the code snippets posted above and I don’t think any of them work. Here code that will work:

extension NEFilterFlow {

    /// A wrapper around `sourceAppAuditToken` that returns a value of the right type.
    ///
    /// - Note: I’d normally write this code in a much more compact fashion but
    /// I’ve expanded it out so that I can comment each step.

    var sourceAppAuditTokenQ: audit_token_t? {

        // The following lines check whether the `sourceAppAuditToken` value is
        // missing, returning `nil` in that case.
        //
        // The size check is a good idea in general, but it’s particularly
        // important because of the way that we set up `pRaw`.  See the comments
        // below for more details.

        guard
            let tokenData = self.sourceAppAuditToken,
            tokenData.count == MemoryLayout<audit_token_t>.size
        else {
            return nil
        }

        // Here we use `withUnsafeBytes(_:)` to call a closure (the stuff inside
        // the curly brackets) with an `UnsafeRawBufferPointer` that represents
        // the bytes in the `tokenData` value.  This `buf` value is, as the type
        // name suggests, a way to represent a buffer of raw bytes.

        return tokenData.withUnsafeBytes { (buf: UnsafeRawBufferPointer) -> audit_token_t in

            // Here we set `pRaw` to a pointer to the base address of that
            // buffer.  Note the force unwrap (`!`).  That’s necessary because
            // `buf.baseAddress` is optional, that is, it might be `nil`.  That
            // can only happen if the buffer is empty.  Thus, this force unwrap
            // is safe because of the size check that we did earlier.

            let pRaw = buf.baseAddress!

            // He we convert the raw pointer to a typed pointer.  The
            // `assumingMemoryBound(to:)` routine is something that you should
            // approach with _extreme_ caution.  See its doc comments for an
            // explanation as to why.  In this case, however, its the right
            // thing to do, because the framework guarantees that the buffer
            // contains an valid `audit_token_t`.

            let pToken = pRaw.assumingMemoryBound(to: audit_token_t.self)

            // He we dereference our typed pointer to get the actual value.

            let result = pToken.pointee

            // Finally, we return that value from our closure.  This becomes the
            // result of the `withUnsafeBytes(_:)` call, which ultimately
            // becomes the result of our property getter.

            return result
        }
    }
}

Clearly this is very longwinded, so here’s a version that shows how I might write this in production code:

var sourceAppAuditTokenQ: audit_token_t? {
    guard
        let tokenData = self.sourceAppAuditToken,
        tokenData.count == MemoryLayout<audit_token_t>.size
    else { return nil }
    return tokenData.withUnsafeBytes { buf in
        buf.baseAddress!.assumingMemoryBound(to: audit_token_t.self).pointee
    }
}

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you, Quinn. This is going onto my notes page right now 🙂

I wanted to suggest an alternative approach here, namely to add an initialiser to audit_token_t:

extension audit_token_t {

    init?(data: Data) {
        guard data.count == MemoryLayout<audit_token_t>.size else { return nil }
        self = data.withUnsafeBytes { buf in
            buf.baseAddress!.load(as: audit_token_t.self)
        }
    }
}

These days I prefer this option because I can use it in more circumstances. For example, NEFilterFlow now has a second audit token property, sourceProcessAuditToken, and this initialiser makes it easy to work with either.

Oh, and this code uses load(fromByteOffset:as:), which I find to be very convenient.

But, who knows, maybe in another four years I’ll have changed my mind again and present yet another new option (-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Get Process ID (pid) from NEFilterFlow &amp; sourceAppAuditToken
 
 
Q