test: reproduce native import retention without managed caller

This commit is contained in:
Joseph Doherty
2026-11-10 17:39:00 -05:00
parent 041189aa6d
commit 1298ecc194
14 changed files with 619 additions and 29 deletions
@@ -130,13 +130,19 @@ static void load_bridge(const char *path){
#undef LOAD
image_guard();
}
struct copy_result {const char *stage;int32_t status;int present,associated;};
struct copy_result {
const char *stage,*action;int32_t status;int present,associated;
unsigned owners_before,owners_after;int native_owner_before,native_owner_after;
};
struct operation_state {
SecCertificateRef cert,copied;SecIdentityRef identity;SecKeyRef modern,public_key,legacy;
CFDataRef external;SecKeychainRef keychain,keychain_identity,outer;CFErrorRef error;
char path[PATH_MAX],backing[PATH_MAX];
unsigned logical_owners;int initial_owner,cert_tracked,copied_tracked,identity_tracked;
int failed,path_verified,deleted,delete_during_identity_untrack,fallback_cleanup;
int failed,path_verified,deleted,fallback_cleanup,import_state_released,leaf_checked,leaf_matches;
unsigned import_owners_before,import_owners_after;
const char *delete_stage;
int delete_copy_live,delete_cert_live,delete_copied_live,delete_identity_live,delete_legacy_live;
int32_t cert_return,cert_status,modern_return,create_status,lock_status,external_return,key_return,key_status,move_return,move_status,copy_cert_status,delete_status,path_status;
CFIndex modern_error,external_error;int modern_error_present,external_error_present;unsigned copy_count;
struct copy_result copies[16];
@@ -146,18 +152,19 @@ static int private_path(struct operation_state *s,SecKeychainRef keychain){
char actual[PATH_MAX];memset(actual,0,sizeof(actual));int32_t status=keychain_path(keychain,actual);
return status==0 && (!strcmp(actual,s->path)||!strcmp(actual,s->backing));
}
static void delete_owner(struct operation_state *s,const char *stage){
static void delete_owner(struct operation_state *s,const char *stage,int copy_live){
if(!s->keychain)return;
if(s->deleted){s->failed=1;return;}
s->path_verified=private_path(s,s->keychain);
if(s->path_verified){s->delete_status=delete_chain(s->keychain);s->deleted=1;}
if(!s->path_verified||s->delete_status)s->failed=1;
s->delete_during_identity_untrack=!strcmp(stage,"release-identity")&&s->identity!=NULL&&s->copied==NULL;
s->delete_stage=stage;s->delete_copy_live=copy_live;s->delete_cert_live=s->cert!=NULL;
s->delete_copied_live=s->copied!=NULL;s->delete_identity_live=s->identity!=NULL;s->delete_legacy_live=s->legacy!=NULL;
DROP(s->keychain);released_refs++;
}
static void relinquish(struct operation_state *s,const char *stage){
static void relinquish(struct operation_state *s,const char *stage,int copy_live){
if(!s->logical_owners){s->failed=1;return;}
if(--s->logical_owners==0)delete_owner(s,stage);
if(--s->logical_owners==0)delete_owner(s,stage,copy_live);
}
static SecKeychainRef copied_chain(struct operation_state *s,CFTypeRef item,const char *stage,int *associated){
*associated=0;if(!item)return NULL;
@@ -165,20 +172,29 @@ static SecKeychainRef copied_chain(struct operation_state *s,CFTypeRef item,cons
SecKeychainRef result=NULL;int32_t status=copy_chain(item,&result);owned(result);
int type_ok=!result||CFGetTypeID(result)==SecKeychainGetTypeID();
int belongs=result&&type_ok&&s->keychain_identity==result&&private_path(s,result);
if(s->copy_count<16)s->copies[s->copy_count++]=(struct copy_result){stage,status,result!=NULL,belongs};else s->failed=1;
if(s->copy_count<16)s->copies[s->copy_count++]=(struct copy_result){stage,"lookup",status,result!=NULL,belongs,
s->logical_owners,s->logical_owners,s->keychain!=NULL,s->keychain!=NULL};else s->failed=1;
if(status || !type_ok || (result&&!belongs))s->failed=1;
*associated=status==0&&belongs;return result;
}
static void record_copy_action(struct operation_state *s,unsigned index,const char *action){
if(s->copy_count==index+1){struct copy_result *copy=&s->copies[index];copy->action=action;
copy->owners_after=s->logical_owners;copy->native_owner_after=s->keychain!=NULL;}
}
static void track(struct operation_state *s,CFTypeRef item,const char *stage,int *tracked){
int associated=0;SecKeychainRef copy=copied_chain(s,item,stage,&associated);
unsigned index=s->copy_count;int associated=0;SecKeychainRef copy=copied_chain(s,item,stage,&associated);
// Installed TrackItem does nothing for a null copy; never invent an owner.
if(associated){if(*tracked||!s->keychain)s->failed=1;else{s->logical_owners++;*tracked=1;}}
DROP(copy);
record_copy_action(s,index,"track");DROP(copy);
}
static void untrack(struct operation_state *s,CFTypeRef item,const char *stage,int *tracked){
if(!item)return;
int associated=0;SecKeychainRef copy=copied_chain(s,item,stage,&associated);
if(*tracked){if(associated){*tracked=0;relinquish(s,stage);}else s->failed=1;}
else if(associated)s->failed=1;
unsigned index=s->copy_count;int associated=0;SecKeychainRef copy=copied_chain(s,item,stage,&associated);
if(*tracked){if(associated){*tracked=0;relinquish(s,stage,copy!=NULL);}else s->failed=1;}
// A still-registered private keychain must never consume an untracked owner.
// After deletion the installed managed registry has no entry to release.
else if(associated&&s->keychain)s->failed=1;
record_copy_action(s,index,"untrack");
// Final logical-owner release above precedes this copy's CFRelease.
DROP(copy);
}
@@ -222,23 +238,25 @@ static void import_operation(unsigned iteration,const unsigned char *der,size_t
if(s.failed)goto cleanup;
s.move_return=move_key(s.cert,s.keychain,s.legacy,&s.identity,&s.move_status);owned(s.identity);
track(&s,s.cert,"move-cert-track",&s.cert_tracked);track(&s,s.identity,"move-identity-track",&s.identity_tracked);DROP(s.outer);
if(s.move_return!=1||s.move_status||!s.identity||CFGetTypeID(s.identity)!=SecIdentityGetTypeID()||!s.cert_tracked||!s.identity_tracked||s.failed){s.failed=1;goto cleanup;}
if(s.move_return!=1||s.move_status||!s.identity||CFGetTypeID(s.identity)!=SecIdentityGetTypeID()||s.failed){s.failed=1;goto cleanup;}
s.copy_cert_status=copy_cert(s.identity,&s.copied);owned(s.copied);track(&s,s.copied,"identity-cert-track",&s.copied_tracked);
if(s.copy_cert_status||!s.copied||CFGetTypeID(s.copied)!=SecCertificateGetTypeID()||!s.copied_tracked||s.failed){s.failed=1;goto cleanup;}
if((iteration==1||iteration==REPETITIONS)&&!leaf_equal(s.copied,der,der_length)){s.failed=1;goto cleanup;}
if(s.copy_cert_status||!s.copied||CFGetTypeID(s.copied)!=SecCertificateGetTypeID()||s.failed){s.failed=1;goto cleanup;}
if(iteration==1||iteration==REPETITIONS){s.leaf_checked=1;s.leaf_matches=leaf_equal(s.copied,der,der_length);
if(!s.leaf_matches){s.failed=1;goto cleanup;}}
cleanup:
wipe(password,sizeof(password));DROP(s.error);DROP(s.external);DROP(s.modern);DROP(s.public_key);DROP(s.outer);
untrack(&s,s.cert,"release-original-cert",&s.cert_tracked);DROP(s.cert);
if(s.initial_owner){s.initial_owner=0;relinquish(&s,"release-import-state");}
if(s.initial_owner){s.initial_owner=0;s.import_state_released=1;s.import_owners_before=s.logical_owners;
relinquish(&s,"release-import-state",0);s.import_owners_after=s.logical_owners;}
untrack(&s,s.copied,"release-copied-cert",&s.copied_tracked);DROP(s.copied);
untrack(&s,s.identity,"release-identity",&s.identity_tracked);DROP(s.identity);
DROP(s.legacy); // Explicit lifetime; managed helper does not scope this local with using.
if(s.keychain){s.failed=1;s.fallback_cleanup=1;delete_owner(&s,"failure-fallback-after-items");}
if(s.keychain){s.failed=1;s.fallback_cleanup=1;delete_owner(&s,"failure-fallback-after-items",0);}
unsigned made=all_created-before_created,released=all_released-before_released;
if(made!=released||s.logical_owners||s.cert_tracked||s.copied_tracked||s.identity_tracked)s.failed=1;
if(!s.failed&&(s.copy_count!=9||!s.delete_during_identity_untrack||!s.deleted))s.failed=1;
fprintf(stdout,"{\"kind\":\"operation\",\"iteration\":%u,\"certReturn\":%d,\"certStatus\":%d,\"modernReturn\":%d,\"modernErrorCode\":%ld,\"modernErrorPresent\":%s,\"createStatus\":%d,\"pathStatus\":%d,\"neverLockStatus\":%d,\"externalReturn\":%d,\"externalErrorCode\":%ld,\"externalErrorPresent\":%s,\"legacyReturn\":%d,\"legacyStatus\":%d,\"moveReturn\":%d,\"moveStatus\":%d,\"copyCertStatus\":%d,\"deleteStatus\":%d,\"privatePathVerified\":%s,\"deleteDuringFinalIdentityUntrack\":%s,\"fallbackCleanup\":%s,\"logicalOwnersRemaining\":%u,\"ownedOutputs\":%u,\"releasedOutputs\":%u,\"failed\":%s,\"copies\":[",iteration,s.cert_return,s.cert_status,s.modern_return,(long)s.modern_error,s.modern_error_present?"true":"false",s.create_status,s.path_status,s.lock_status,s.external_return,(long)s.external_error,s.external_error_present?"true":"false",s.key_return,s.key_status,s.move_return,s.move_status,s.copy_cert_status,s.delete_status,s.path_verified?"true":"false",s.delete_during_identity_untrack?"true":"false",s.fallback_cleanup?"true":"false",s.logical_owners,made,released,s.failed?"true":"false");
for(unsigned i=0;i<s.copy_count;i++){struct copy_result c=s.copies[i];fprintf(stdout,"%s{\"stage\":\"%s\",\"status\":%d,\"returnedRef\":%s,\"privateAssociation\":%s}",i?",":"",c.stage,c.status,c.present?"true":"false",c.associated?"true":"false");}
if(!s.failed&&(s.copy_count!=9||!s.import_state_released||!s.deleted))s.failed=1;
fprintf(stdout,"{\"kind\":\"operation\",\"iteration\":%u,\"certReturn\":%d,\"certStatus\":%d,\"modernReturn\":%d,\"modernErrorCode\":%ld,\"modernErrorPresent\":%s,\"createStatus\":%d,\"pathStatus\":%d,\"neverLockStatus\":%d,\"externalReturn\":%d,\"externalErrorCode\":%ld,\"externalErrorPresent\":%s,\"legacyReturn\":%d,\"legacyStatus\":%d,\"moveReturn\":%d,\"moveStatus\":%d,\"copyCertStatus\":%d,\"deleteStatus\":%d,\"privatePathVerified\":%s,\"deleteStage\":\"%s\",\"deleteLiveReferences\":{\"copy\":%s,\"originalCertificate\":%s,\"copiedCertificate\":%s,\"identity\":%s,\"legacyKey\":%s},\"importStateReleased\":%s,\"importOwnersBefore\":%u,\"importOwnersAfter\":%u,\"leafIdentityChecked\":%s,\"leafIdentityMatches\":%s,\"fallbackCleanup\":%s,\"logicalOwnersRemaining\":%u,\"ownedOutputs\":%u,\"releasedOutputs\":%u,\"failed\":%s,\"copies\":[",iteration,s.cert_return,s.cert_status,s.modern_return,(long)s.modern_error,s.modern_error_present?"true":"false",s.create_status,s.path_status,s.lock_status,s.external_return,(long)s.external_error,s.external_error_present?"true":"false",s.key_return,s.key_status,s.move_return,s.move_status,s.copy_cert_status,s.delete_status,s.path_verified?"true":"false",s.delete_stage?s.delete_stage:"not-attempted",s.delete_copy_live?"true":"false",s.delete_cert_live?"true":"false",s.delete_copied_live?"true":"false",s.delete_identity_live?"true":"false",s.delete_legacy_live?"true":"false",s.import_state_released?"true":"false",s.import_owners_before,s.import_owners_after,s.leaf_checked?"true":"false",s.leaf_matches?"true":"false",s.fallback_cleanup?"true":"false",s.logical_owners,made,released,s.failed?"true":"false");
for(unsigned i=0;i<s.copy_count;i++){struct copy_result c=s.copies[i];fprintf(stdout,"%s{\"stage\":\"%s\",\"action\":\"%s\",\"status\":%d,\"returnedRef\":%s,\"privateAssociation\":%s,\"logicalOwnersBefore\":%u,\"logicalOwnersAfter\":%u,\"nativeOwnerBefore\":%s,\"nativeOwnerAfter\":%s}",i?",":"",c.stage,c.action,c.status,c.present?"true":"false",c.associated?"true":"false",c.owners_before,c.owners_after,c.native_owner_before?"true":"false",c.native_owner_after?"true":"false");}
fprintf(stdout,"]}\n");fflush(stdout);if(s.failed)die("Native bridge import operation failed after ownership cleanup");
}
static unsigned char *read_fixture(const char *name,size_t *length){