(Sorry this is verbose) I am experimenting with adding OpenSSL support to a Cocoa application written in Objective C for OS X 10.6 (Snow Leopard). To simplify matters, I have a small wrapper class which holds the various BIO and cipher context structures, AETOpenSSLWrapper. It looks like the following
.h
@interface AETOpenSSLWrapper: public NSObject
{
BIO *writeBIO,encBIO;
EVP_CIPHER_CTX *ctx;
unsigned char *readWriteBuff;
}
@property (readwrite,assign) BIO *writeBIO,*encBIO;
@property (readwrite,assign) EVP_CIPHER_CTX *ctx;
@property (readwrite,assign) unsigned char *readWriteBuff;
-(id)init;
...
-(void)dealloc;
@end
.m
@implementation AETOpenSSLWrapper
@synthesize writeBIO,encBIO,ctx,readWriteBuff;
-(id)init
{
self=[super init];
if(self)
{
writeBIO=BIO_new(BIO_s_file());
encBIO=...
ctx=...
buff=...
(error handling omitted)
}
return self;
}
@end
Then various utility methods to chain BIOs, write to the output BIO, flush etc, in particular, one -(void)pushEncryptingBIO to chain the enciphering filter BIO (which has been initialised with key, salt and initial vector)
-(void)pushEncryptingBIO
{
writeBIO=BIO_push(encBIO,writeBIO);
}
Finally there is my dealloc routine. This is lifted directly from the enc program supplied with the openssl-1.0.1c distribution
-(void)dealloc
{
if(readWriteBuff!=NULL)
OPENSSL_free(readWriteBuff);
if(writeBIO!=NULL)
BIO_free_all(writeBIO);
if(encBIO!=NULL) <----------- this looks wrong
BIO_free(encBIO); <---+
[super dealloc];
}
The equivalent code is prior to the loop that encrypts the input buffer and at the end of the MAIN() routine in apps/enc.c in the openssl source tree:
lines 657 – 658
if (benc != NULL)
wbio=BIO_push(benc,wbio);
and lines 682 – 688
end:
ERR_print_errors(bio_err);
if (strbuf != NULL) OPENSSL_free(strbuf);
if (buff != NULL) OPENSSL_free(buff);
if (in != NULL) BIO_free(in);
if (out != NULL) BIO_free_all(out);
if (benc != NULL) BIO_free(benc); <--- are we sure about this?
The question is (finally): should that call to BIO_free(benc) (or BIO_free(encBIO) in my code) be there since the BIO is pushed onto the writeBIO/out chain, which is freed with BIO_free_all? Looking at the implementation of BIO_free_all, it just runs down the BIO chain, decrementing ref counts and freeing as it goes and does not NULL out the pointers. This looks like it must be a bug, but obviously I am reluctant to assume the SSL maintainers have missed this and I haven’t. I get occasional crashes (1 in 10) if I leave the BIO_free(encBIO) call in and not if I don’t, but I don’t want a leak. This is in an Apple Event Handler, so debugging is complicated further. Any suggestions?
I’ve had the opportunity to work with BIOs some time ago and I think you are correct. From OpenSSL man pages:
Also, since OpenSSL cleanup functions take a pointer to the structure, they can’t change the value of the pointer (i.e it would require the address of the pointer to do so). Even if OpenSSL set the pointer parameter to NULL, only the copy would be NULL, turning out to be an useless behavior. Thus it’s up to you to set it to NULL.
To give you a concrete example, the following C++ code is used to encrypt a plain text using PKCS#5 standard. It receives a password as parameter, which will be used to derive an AES key. The following code does not leak (checked with valgrind).
As you can see, I’ve chained three BIOs, and the single call to BIO_free_all(benc) cleans up all BIOs.
Regards.