Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codesign w/ debugging entitlement for backtraces on macOS #7010

Merged
merged 13 commits into from Oct 23, 2023

Conversation

MaxDesiatov
Copy link
Member

@MaxDesiatov MaxDesiatov commented Oct 16, 2023

Motivation:

The new Swift backtracer will only function on macOS if:

  1. SWIFT_BACKTRACE environment variable has its enable option set to yes or tty, and

  2. The program you are running was signed with the com.apple.security.get-task-allow entitlement.

Xcode automatically signs all local builds with that entitlement by default; without it the Swift backtracer won't work.

SwiftPM should sign Debug binaries with the entitlement by default. It should also have an option to sign Release binaries with the entitlement, noting that they should not be distributed in that state but that crashes may only manifest in release builds (where the optimizer is fully enabled).

Modifications:

Added new --enable-get-task-allow-entitlement and --disable-get-task-allow-entitlement CLI flags. Made it enabled by default for debug builds on macOS. When enabled, applying codesigning with com.apple.security.get-task-allow in LLBuildManifestBuilder+Product.swift.

Result:

Backtraces work on macOS when built with SwiftPM.

Resolves rdar://112065568.

@MaxDesiatov
Copy link
Member Author

@swift-ci test

@neonichu
Copy link
Member

  1. I'm a little bit concerned by us having to duplicate all binaries to achieve this. I'm not 100% sure how this works in Xcode's build system, but I believe it doesn't require this kind of workaround. cc @m-i-r-z-a @owenv
  2. Not sure about the naming here, I don't feel like I would understand that --enable-unsafe-debugging does what it does here. Maybe we should just call it --enable-backtraces and make it an error to use on non-macOS since it won't do anything there today.

@MaxDesiatov
Copy link
Member Author

MaxDesiatov commented Oct 16, 2023

  1. I'm a little bit concerned by us having to duplicate all binaries to achieve this.

It doesn't duplicate binaries though, only writes the linked binary to a different location when backtraces are enabled. We move the binary to the final location after codesigning is completed to satisfy the inputs of the phony product rule. So far this is the only way I found to make it work for shell commands that don't have outputs different from inputs (like the codesign command).

@MaxDesiatov
Copy link
Member Author

@swift-ci test

@owenv
Copy link
Contributor

owenv commented Oct 16, 2023

llbuild has an is-mutated key which can be used in combination with virtual output nodes to order a series of tasks which modify inputs in-place. There's some brief documentation of it here: https://github.com/apple/swift-llbuild/blob/a3b8d34be319f1b3ba8de53c434386419dec1f4f/docs/buildsystem.rst#L387

@tomerd
Copy link
Member

tomerd commented Oct 16, 2023

llbuild has an is-mutated key which can be used in combination with virtual output nodes to order a series of tasks which modify inputs in-place. There's some brief documentation of it here: https://github.com/apple/swift-llbuild/blob/a3b8d34be319f1b3ba8de53c434386419dec1f4f/docs/buildsystem.rst#L387

this may also be interesting in the context of plugins? cc @neonichu @abertelrud

@al45tair
Copy link
Contributor

llbuild has an is-mutated key which can be used in combination with virtual output nodes to order a series of tasks which modify inputs in-place.

This sounds like the right way to make this work.

Also, I wonder whether it would be worth providing more general support for code signing and entitlements in SwiftPM, so that e.g. a user could specify in the package manifest that they'd like their program signed, and maybe provide a list of entitlements they'd like it to have?

@@ -367,6 +367,10 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription

return flags
}

func codeSigningArguments(plistPath: AbsolutePath, binaryPath: AbsolutePath) -> [String] {
["codesign", "--force", "--sign", "-", "--entitlements", plistPath.pathString, binaryPath.pathString]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a good idea to have a command line option to let you specify the code signing identity (here we're using -).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO that should come as a separate PR to make this facility a bit more general.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I only mention it because people who are aware of how this works will pretty quickly notice that we're signing things and ask how to sign with a different identity.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair, but signing with a different identity or with a different entitlement requires a public API design and likely an evolution proposal. Scoping this to one specific case means we don't have to block backtraces with this potential future design work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. No objection from me.

@neonichu
Copy link
Member

Also, I wonder whether it would be worth providing more general support for code signing and entitlements in SwiftPM, so that e.g. a user could specify in the package manifest that they'd like their program signed, and maybe provide a list of entitlements they'd like it to have?

I think that would be useful, but in my mind, it's very separable from this. We need to design APIs for configuring this etc.

MaxDesiatov added a commit that referenced this pull request Oct 18, 2023
Dependency of #7010

Added per the llbuild documentation at https://github.com/apple/swift-llbuild/blob/a3b8d34be319f1b3ba8de53c434386419dec1f4f/docs/buildsystem.rst#L387 and a corresponding test at https://github.com/apple/swift-llbuild/blob/a3b8d34be319f1b3ba8de53c434386419dec1f4f/tests/BuildSystem/Build/mutable-outputs.llbuild#L66.

I also took the opportunity to get rid of deprecated TSC dependencies in `ManifestWriter.swift`, replacing it with string interpolation instead.
MaxDesiatov added a commit that referenced this pull request Oct 18, 2023
Dependency of #7010.

Added per the llbuild documentation at https://github.com/apple/swift-llbuild/blob/a3b8d34be319f1b3ba8de53c434386419dec1f4f/docs/buildsystem.rst#L387 and a corresponding test at https://github.com/apple/swift-llbuild/blob/a3b8d34be319f1b3ba8de53c434386419dec1f4f/tests/BuildSystem/Build/mutable-outputs.llbuild#L66.

Newly introduced attributes are not used anywhere yet in this PR, so technically it is NFC.

I also took the opportunity to get rid of deprecated TSC dependencies in `ManifestWriter.swift`, replacing it with string interpolation instead.
@al45tair
Copy link
Contributor

  1. Not sure about the naming here, I don't feel like I would understand that --enable-unsafe-debugging does what it does here. Maybe we should just call it --enable-backtraces and make it an error to use on non-macOS since it won't do anything there today.

If we're calling it --enable-backtraces, I think it should work on Linux as well, since someone might have set SWIFT_BACKTRACE=enable=no in their environment, and in that case it could just set enable=yes instead.

The reasoning behind --enable-unsafe-debugging was that it will also allow the use of various other tools (like swift-inspect) which have the same requirement for entitlements that the backtracer has. But that has the downside that it's more macOS specific, whereas --enable-backtraces makes some sense for swift run on Linux too.

@al45tair
Copy link
Contributor

Note: if we're changing SWIFT_BACKTRACE, it should leave any other settings alone (it's a comma-separated list of key=value settings).

@neonichu
Copy link
Member

My objections to the current name are:

  • it only does backtraces but has a generic name
  • it doesn't say that it is macOS only, so users may think they're getting something which they are not

If we can make it explicitly macOS-only and have future plans for more functionality, the name seems fine to me.

@al45tair
Copy link
Contributor

al45tair commented Oct 19, 2023

I don't mind --enable-backtraces, to be clear. If we do that, it can just work everywhere. It will as a side effect also make e.g. swift-inspect work on macOS.

@MaxDesiatov
Copy link
Member Author

Yeah, there's a case for --disable-backtraces to be respected on Linux, otherwise we'd have to call it --enable-macos-backtraces and --disable-macos-backtraces for clarity, and I'd prefer the former.

@MaxDesiatov
Copy link
Member Author

MaxDesiatov commented Oct 19, 2023

IIUC @tomerd prefers --enable-get-task-allow-entitlement and --disable-get-task-allow-entitlement flags to control this. Otherwise I hope we arrived at a consensus?

@MaxDesiatov
Copy link
Member Author

@swift-ci test

@MaxDesiatov
Copy link
Member Author

@swift-ci test windows

@MaxDesiatov
Copy link
Member Author

@swift-ci test

@MaxDesiatov
Copy link
Member Author

@swift-ci test windows

@MaxDesiatov
Copy link
Member Author

@swift-ci test

@MaxDesiatov
Copy link
Member Author

@swift-ci test

@MaxDesiatov
Copy link
Member Author

@swift-ci test

@MaxDesiatov
Copy link
Member Author

@swift-ci test windows

@MaxDesiatov
Copy link
Member Author

@swift-ci test

@MaxDesiatov
Copy link
Member Author

@swift-ci test

@MaxDesiatov
Copy link
Member Author

@swift-ci test windows

@MaxDesiatov MaxDesiatov enabled auto-merge (squash) October 20, 2023 18:14
public var enableGetTaskAllowEntitlement: Bool = false

@Flag(help: .hidden)
public var disableGetTaskAllowEntitlement: Bool = false
Copy link
Member

@tomerd tomerd Oct 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can probably marge this into one optional Bool setting

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look @Flag supports optional types, unless I'm something? Maybe @natecook1000 or @rauhul can clarify?

Copy link
Contributor

@rauhul rauhul Oct 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between true/false/nil behaviors?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC I had to provide a default value, but the default value depended on values of other options. I assume false would mean --disable..., true would mean --enable..., and nil would mean neither were passed on CLI?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm so you want to infer true/false based on other flags unless explicitly specified... that is reasonable. I don't think we support Optional Flags yet, but I wonder if you could use an EnumerableFlag instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can actually have a boolean flag as long as there's an inversion, which I think is exactly what you want in this case. The declaration would look like:

@Flag(inversion: .prefixedEnableDisable)
var getTaskAllowEntitlement: Bool?

It looks like we might not allow an = nil in there, which may have been what you ran into. That's also an issue we should be able to fix pretty easily.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh good catch!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this is what I had in mind. @MaxDesiatov

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, it actually does support an optional default value, at least with .prefixedEnableDisable it works great. I'll submit a follow-up PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #7028

Copy link
Member

@tomerd tomerd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems good to me

@MaxDesiatov MaxDesiatov merged commit 855b129 into main Oct 23, 2023
5 checks passed
@MaxDesiatov MaxDesiatov deleted the maxd/debugging-entitlement branch October 23, 2023 17:09
@@ -33,13 +33,19 @@ extension LLBuildManifestBuilder {
testInputs = []
}

// Create a phony node to represent the entire target.
let targetName = try buildProduct.product.getLLBuildTargetName(config: self.buildConfig)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please check if this has performance implications, I vaguely remeber @neonichu ran into something like that with this API, tho no 100% sure

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This phony node is not new, it's only moved from previous L62 where it was declared too late for us. I had to move it up to this line.

MaxDesiatov added a commit that referenced this pull request Oct 25, 2023
### Motivation:

Following up on feedback to #7010, it's better to have a single optional boolean option than two non-optional booleans.

### Modifications:

Replaced `var enableGetTaskAllowEntitlement = false` and `var disableGetTaskAllowEntitlement = false` with `var getTaskAllowEntitlement: Bool? = nil`, which has an additional `inversion: .prefixedEnableDisable` configuration.

### Result:

Code handling these flags is simpler and easier to read.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants