ControlPlane 1.6.7 verifies the signature of a tool before installing it, but the path reaches a shell through system() first. If the path contains command substitution, evaluation happens before codesign checks the file. A later verification failure does not undo that evaluation.
The target is the historical macOS x86_64 application and CPHelperTool. Fixed-revision source, recorded runtime evidence, and local models serve different purposes here: establish the authorization conditions for reaching the handler, then trace how a string becomes a privileged operation. One successful run does not establish the outcome for every user state.
Version, installation, and runtime identity
| Item | Value |
|---|---|
| Application version | 1.6.7 |
| Release date (UTC) | 2019-08-21 |
| Commit | 9ae1973647d09d0078188b0cbd201bc00f3ddce5 |
| Helper CFBundleVersion | 8 |
| Analysis record date | 2025-07-15 |
The product release and the analysis belong to different timelines. The official release page identifies the 2019 version. Source references below use that commit rather than today's default branch. A specific fixed release has not been established.
- 1
Copy the application
Placing the app in Applications deploys the client; it does not by itself establish a root service.
- 2
Initialize the context
Creating a default context configures the application. It is distinct from installing the helper.
- 3
Install the helper
The client checks the existing helper and version. When needed, it requests kSMRightBlessPrivilegedHelper and invokes SMJobBless.

Interface illustration: application setup and privileged-component installation are separate stages.
The installation implementation queries the job with SMJobCopyDictionary and checks versions and code requirements. The installation trust relationship determines which application can install which helper. It does not automatically authenticate every subsequent custom Unix-socket request.
Locate the endpoint through plists and sections
- /Library/PrivilegedHelperTools
- com.dustinrue.CPHelperTool
- /var/run
- com.dustinrue.CPHelperTool.socket
The runtime record shows these endpoints. Socket presence establishes visibility, not successful access; permissions and connection results still matter. The following view combines the launchd socket configuration and the helper's SMAuthorizedClients from two separate plists. It is not a complete replacement file.
SockPathMode = 438 is octal 0666. This mode does not restrict access to a particular ordinary-user group, but path permissions, peer checks, and request authorization remain separate conditions. The SMAuthorizedClients requirement governs installation; a custom message handler does not inherit equivalent caller validation automatically.
- Arch
- x86_64
| Name | Address | Size | Offset |
|---|---|---|---|
| __TEXT.__info_plist | 0x1000073AB | 0x270 | 0x000073AB |
| __TEXT.__launchd_plist | 0x10000761B | 0x29C | 0x0000761B |
The section values come from the historical otool record: 0x270 = 624, 29611 + 624 = 30235, and the next section is 0x29c = 668 bytes. File offsets and virtual addresses are separate quantities. These offsets describe one binary layout; extraction must also check the file length and the matching architecture slice.
Reusing the client does not grant more rights
| Object | Architecture | CodeDirectory flags | TeamIdentifier |
|---|---|---|---|
| ControlPlane | x86_64 | 0x0 | YV4RHGCYFA |
| CPHelperTool | x86_64 | 0x0 | YV4RHGCYFA |
Both records name Developer ID Application: Dustin Rue. The application's flags=0x0 is consistent with Hardened Runtime being absent. The historical run loaded a library through DYLD_INSERT_LIBRARIES, but that flag value alone does not guarantee injection across every system, process type, or launch method.
flowchart LR accTitle: Client reuse and helper boundaries accDescr: A library runs in the client, reuses helper initialization and BAS transport, and reaches the helper's separate authorization and command handler. A["Library in client"] --> B["helperToolInit:"] B --> C["BAS request"] C --> D["Unix socket"] D --> E["Per-command authorization"] E --> F["Privileged handler"]
The library replaced ToggleRemoteLoginAction's execute: implementation and invoked the method directly. The useful machinery was helperToolInit:, helper identification, and BAS transport, not the remote-login operation itself. A replacement must still match the Objective-C ABI for self, _cmd, and explicit arguments.
helperToolInit: creates an AuthorizationRef and invokes BASSetDefaultRules. A valid authorization reference provides access to an authorization session; it is not a grant of every command right. Reusing the client's protocol code also leaves the server's checks in place.
The command table selects the authorization branch
flowchart TD
accTitle: Helper dispatch and conditional authorization
accDescr: Main enters BASHelperToolMain and HandleConnection. A valid command with a named right must pass AuthorizationCopyRights before its callback runs.
A["main"] --> B["BASHelperToolMain"]
B --> C["HandleConnection"]
C --> D["BASRead external form"]
D --> E["AuthorizationCreateFromExternalForm"]
E --> F["BASReadDictionary"]
F --> G["FindCommand"]
G --> H{"Found and rightName set?"}
H -->|"yes"| I["AuthorizationCopyRights"]
H -->|"no"| J{"commandProcStatus == noErr?"}
I --> J
J -->|"yes"| K["commandProcs[index]"]
J -->|"no"| L["Error response"]
K --> M["Response and cleanup"]
L --> M
This summarizes successful reads in BAS connection handling. Read and allocation failures can enter error handling earlier. The command and callback tables correspond by index. When a matched command has a rightName, the helper calls AuthorizationCopyRights first and invokes the callback only if commandProcStatus == noErr.
| Command | rightName | Source default rule |
|---|---|---|
| InstallTool | com.dustinrue.ControlPlane.InstallTool | default |
| SetDisplaySleepTime | com.dustinrue.ControlPlane.SetDisplaySleepTime | allow |
default and allow are different rules. The source defaults initialize missing right definitions; the live authorization database still needs inspection, because existing definitions need not be replaced. If rightName is absent, the framework skips this check and the handler is responsible for any applicable authorization.
The flags include kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed. The client BAS path also contains preauthorization. Client preauthorization, the server's recheck, and whether the user sees a prompt are distinct events. Policy and cached credentials affect interaction; Apple's authorization guide distinguishes authentication from granting an operation's rights.
A local Boolean model checked all eight combinations of command presence, a named right, and a granted right. It validates this branch structure only. Cold starts, expired cached credentials, and different macOS user identities have not been exercised as a complete matrix. Reaching DoInstallTool therefore retains the prerequisite that its rights check has passed.
The shell runs before signature verification
DoInstallTool reads srcPath and toolName from the request and converts the path to a C string. This command-construction excerpt has been line-wrapped; it illustrates the data boundary rather than reproducing the entire function.
asprintf(&valCodeSignCmd,
"codesign -v -R=\"certificate leaf[subject.CN] = \\\"%s\\\" "
"and anchor apple generic\" \"%s\"",
kSigningCertCommonName, pFilename);asprintf allocates the formatted buffer; it does not escape shell syntax in the path. The code then passes the entire string to system(). Double quotes preserve spaces in a filename but still allow $(...) command substitution.
The signature requirement is interpreted by codesign, while the surrounding command is interpreted by a shell first. These are separate language layers. certificate leaf[subject.CN] and anchor apple generic are signing conditions; Apple's requirement-language guide defines the latter. Neither protects the preceding shell expansion.
This minimal model substitutes an ordinary function that always fails for the verifier. It prints a marker and an argument without invoking the system helper or a real signing tool:
#!/usr/bin/env bash
set -u
exec 2>&1
mock_codesign() {
printf 'verifier argument: <%s>\n' "$1"
return 1
}
mock_codesign "$(printf 'substitution ran\n' >&2; printf 'fixture.bin')"
status=$?
printf 'verifier status: %s\n' "$status"
test "$status" -eq 1
substitution ran
verifier argument: <fixture.bin>
verifier status: 1The model ran under MSYS Bash 5.1.16 on Windows. Substitution runs first; the verifier then receives fixture.bin and returns 1. This checks shell command-substitution semantics, not a new execution of macOS system() or the root helper.
Transport success is not installation success
BASExecuteRequestInHelperTool returns a transport status. The response's kBASErrorKey carries the command status, and the installation handler adds a separate Success value. Read all three independently.
bool success = true;
OSStatus retval = noErr;
/* String conversion and allocation succeeded in this excerpt. */
if (system(valCodeSignCmd) == 0) {
OSStatus fsret = FSPathCopyObjectSync(
pFilename, "/usr/local/bin", toolName,
NULL, kFSFileOperationOverwrite);
if (fsret != noErr)
success = false;
}
/* No else branch sets success to false here. */The pinned DoInstallTool implementation has another result-reporting problem. If conversion and allocation succeed but system() returns nonzero, it skips copying without clearing success or changing the initial retval = noErr. A local control-flow model produces these three outcomes:
| Verification status | File copy | retval | Success |
|---|---|---|---|
| Nonzero | Not called | 0 | true |
| 0 | Returns an error | 0 | false |
| 0 | Succeeds | 0 | true |
A success indication can therefore coexist with no copy, or differ from the callback status. It is not independent evidence that a signature passed. The model assumes the stated conversion and allocation success; it does not collapse the function's other error paths into the same behavior.
$ id
uid=0(root) gid=0(wheel) ...The historical terminal separately records uid=0(root), which is stronger evidence of execution identity than a client-generated success message. It still supports that run, not the absence of authorization prerequisites. The interactive channel and download-and-deploy wrapper are unnecessary to explain the root cause.
Fix the interpreter boundary and result propagation
Use code-signing APIs directly, or launch a fixed verifier executable with an argument array so that the filename remains data. Passing authorization does not make every argument trustworthy. Path scope, ownership, and object consistency between verification and copying still require review.
Propagate verifier failure, copy failure, and success distinctly into command and business-level results. Tests should assert the actual filesystem outcome, not just a client's success text. The key distinction is that the signature algorithm was not shown to fail: the broken boundary precedes it, when the path enters the command interpreter.
References
- ControlPlane: release 1.6.7
- ControlPlane: installation and client initialization
- ControlPlane: commands and default right rules
- ControlPlane: BAS connection handling
- ControlPlane: DoInstallTool
- Apple: Authorization Services concepts
- Apple: code signing requirement language
- GNU Bash: command substitution