Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

chore: ensure downloaded slim binary version matches server#211

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ethanndickson merged 1 commit intomainfromethan/validate-slim-binary-version
Aug 6, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletionCoder-Desktop/Coder-DesktopHelper/Manager.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,7 +76,8 @@ actor Manager {
}
pushProgress(stage:.validating)
do{
tryValidator.validate(path: dest)
tryValidator.validateSignature(binaryPath: dest)
tryawaitValidator.validateVersion(binaryPath: dest, serverVersion: buildInfo.version)
}catch{
// Cleanup unvalid binary
try?FileManager.default.removeItem(at: dest)
Expand Down
59 changes: 51 additions & 8 deletionsCoder-Desktop/VPNLib/Validate.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
import Foundation
import Subprocess

publicenumValidationError:Error{
case fileNotFound
Expand All@@ -7,7 +8,9 @@ public enum ValidationError: Error {
case unableToRetrieveSignature
case invalidIdentifier(identifier:String?)
case invalidTeamIdentifier(identifier:String?)
case invalidVersion(version:String?)
case unableToReadVersion(anyError)
case binaryVersionMismatch(binaryVersion:String, serverVersion:String)
case internalError(OSStatus)

publicvardescription:String{
switchself{
Expand All@@ -21,10 +24,14 @@ public enum ValidationError: Error {
"Unable to retrieve signing information."
caselet.invalidIdentifier(identifier):
"Invalid identifier:\(identifier??"unknown")."
caselet.invalidVersion(version):
"Invalid runtimeversion:\(version??"unknown")."
caselet.binaryVersionMismatch(binaryVersion, serverVersion):
"Binaryversion does not match server. Binary:\(binaryVersion), Server:\(serverVersion)."
caselet.invalidTeamIdentifier(identifier):
"Invalid team identifier:\(identifier??"unknown")."
caselet.unableToReadVersion(error):
"Unable to execute the binary to read version:\(error.localizedDescription)"
caselet.internalError(status):
"Internal error with OSStatus code:\(status)."
}
}

Expand All@@ -37,22 +44,32 @@ public class Validator {
publicstaticletminimumCoderVersion="2.24.2"

privatestaticletexpectedIdentifier="com.coder.cli"
// The Coder team identifier
privatestaticletexpectedTeamIdentifier="4399GN35BJ"

// Apple-issued certificate chain
publicstaticletanchorRequirement="anchor apple generic"

privatestaticletsignInfoFlags:SecCSFlags=.init(rawValue: kSecCSSigningInformation)

publicstaticfuncvalidate(path:URL)throws(ValidationError){
guardFileManager.default.fileExists(atPath:path.path)else{
publicstaticfuncvalidateSignature(binaryPath:URL)throws(ValidationError){
guardFileManager.default.fileExists(atPath:binaryPath.path)else{
throw.fileNotFound
}

varstaticCode:SecStaticCode?
letstatus=SecStaticCodeCreateWithPath(pathasCFURL,SecCSFlags(),&staticCode)
letstatus=SecStaticCodeCreateWithPath(binaryPathasCFURL,SecCSFlags(),&staticCode)
guard status== errSecSuccess,let code= staticCodeelse{
throw.unableToCreateStaticCode
}

letvalidateStatus=SecStaticCodeCheckValidity(code,SecCSFlags(),nil)
varrequirement:SecRequirement?
letreqStatus=SecRequirementCreateWithString(anchorRequirementasCFString,SecCSFlags(),&requirement)
guard reqStatus== errSecSuccess,let requirementelse{
throw.internalError(OSStatus(reqStatus))
}

letvalidateStatus=SecStaticCodeCheckValidity(code,SecCSFlags(), requirement)
guard validateStatus== errSecSuccesselse{
throw.invalidSignature
}
Expand All@@ -78,6 +95,32 @@ public class Validator {
}
}

publicstaticletxpcPeerRequirement="anchor apple generic"+ // Apple-issued certificate chain
// This function executes the binary to read its version, and so it assumes
// the signature has already been validated.
publicstaticfunc validateVersion(binaryPath:URL, serverVersion:String)asyncthrows(ValidationError){
guardFileManager.default.fileExists(atPath: binaryPath.path)else{
throw.fileNotFound
}

letversion:String
do{
trychmodX(at: binaryPath)
letversionOutput=tryawaitSubprocess.data(for:[binaryPath.path,"version","--output=json"])
Copy link
Member

Choose a reason for hiding this comment

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

Any way to drop privileges for this subprocess? We are root after all...

Copy link
MemberAuthor

@ethanndicksonethanndicksonAug 4, 2025
edited
Loading

Choose a reason for hiding this comment

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

This is possible with a combination oflaunchctl asuser <uid> andsudo -u [<uid>|<username>]:

sudo -u '#501' launchctl asuser 501 /usr/bin/whoami

One switches the UID, the other the (macOS specific) execution context.

Unfortunately,

$ sudo -u nobody launchctl asuser -2 /usr/bin/whoamiCould not switch to audit session 0x187c3: 1: Operation not permitted

does not work, and so we'd need to determine the currently logged in user in some other way.
501 is the first created user on the machine, and on corporate devices this will likely be some admin user.

Copy link
Member

Choose a reason for hiding this comment

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

It's fine to execute it as root if it's too difficult, the signature is still ours so the risk is small

letparsed:VersionOutput=tryJSONDecoder().decode(VersionOutput.self, from: versionOutput)
version= parsed.version
}catch{
throw.unableToReadVersion(error)
}

guard version== serverVersionelse{
throw.binaryVersionMismatch(binaryVersion: version, serverVersion: serverVersion)
}
}

structVersionOutput:Codable{
letversion:String
}

publicstaticletxpcPeerRequirement= anchorRequirement+
" and certificate leaf[subject.OU] =\""+ expectedTeamIdentifier+"\"" // Signed by the Coder team
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp