patch file attached.

Subitems:

-------------------------                 {
+#if 0 /* [i_a] ERROR: some intrinsic memcpy() ops can copy DOWN, thus
corrupting the data here */
                 memcpy((unsigned char *)ctx->tmp,
                     (unsigned char *)&(ctx->tmp[jj]),i-jj);
+#else
+                    memmove(ctx->tmp, &ctx->tmp[jj], i-jj);
+#endif
                 ctx->tmp_len=i-jj;

Some platforms (MSVC200x with intrinsic functions and maximum speed
optimizations, for example) will produce a memcpy() which does not copy
/upwards/ but rather /downwards/ (counting indexes down to zero, so to
speak). Perfectly legal with respect to the standards; a bit of a bother in
this code as it will blow your desirable data to smithereens. Ouch.



-----------------------------------
 static int b64_write(BIO *b, const char *in, int inl)
     {
-    int ret=inl,n,i;
+    int ret=0; /* [i_a] ERROR: (int)inl; will cause race condition for
nonblock bio returning retry code to us below */
+    int n;
+    int i;
     BIO_B64_CTX *ctx;


was: ret=inl; as a starter;
is: ret=0; as a starter, then add each piece we processed to ret.

Sorry, 'race' is a misnomer; my English isn't always up to snuff 24/7.

The point is:
imagine the BIO_write()s in there (in the loop), being stacked on top of
another BIO, and everybody happily running in NONBLOCKING mode, then /this/
code may receive 'retry' error codes from those BIO_write()s due to the
nonblocky nature of the BIO chain then.
Old code will then run with ret==inl i.e. 'everything done! You can all go
back to sleep now!' which is VERY WRONG when the retry happens halfway
through.

Result:
Severe loss of data; corrupted stream. You're OUT.

How did that happen?
Have another look at those lines
             i=BIO_write(b->next_bio,&(ctx->buf[ctx->buf_off]),n);
             if (i <= 0)
                 {
                 BIO_copy_next_retry(b);
                 return((ret == 0)?i:ret);
                 }
especially the return statement there and given the old 'ret=inl' init,
think it through.

It is much healthier to the BIO chain and caller to report the /actual/
number of bytes /processed/ than just forgetting about the remainder of the
b64 write buffer when a /retry/ signal occurs. Hence the fix:
ret=0;
while (loop)
{
  do_thy_thing(chunk);
  ret += chunk;
}


The OPENSSL_assert() plethora in there has been part of the debugging effort
back then (this has been an old bug, which I never got to filing before :-((
); you may want to keep them as there's additional shite happening when
someone mistakes this filter BIO for a 'bidirectional' one. But that's a
whole another diff (independent read and write channel b64 enc/dec)






-------------------------                 {
Rest of path is a typo fix, introduction of the puts() member for this BIO
(so one can write text strings using BIO_puts() to the BIO chain) and the

     if ((ctx == NULL) || (b->next_bio == NULL)) return(0);

+    BIO_clear_retry_flags(b);
+
     if (ctx->encode != B64_DECODE)

vs.

         outl-=i;
         out+=i;
         }
-    BIO_clear_retry_flags(b);
+    /* BIO_clear_retry_flags(b); */
     BIO_copy_next_retry(b);
     return((ret == 0)?ret_code:ret);
     }

is a fix for the retry flag logic, which, if I'm not mistaken, should always
look like this:

read/write()
{
    BIO_clear_retry_flags(b);
    do_work() / call_next_BIO_in_chain();
    BIO_copy_next_retry(b);
    return ret;
}

instead of what a (very) few BIOs (like this one) have:


read/write()
{
    do_work() / call_next_BIO_in_chain();
    BIO_clear_retry_flags(b);
    BIO_copy_next_retry(b);
    return ret;
}

as the latter will loose any retry flagging done in the do_work() section of
the implementation.



-- 
Met vriendelijke groeten / Best regards,

Ger Hobbelt

--------------------------------------------------
web:    http://www.hobbelt.com/
       http://www.hebbut.net/
mail:   g...@hobbelt.com
mobile: +31-6-11 120 978
--------------------------------------------------

patch file attached.

Subitems:

-------------------------                 {
+#if 0 /* [i_a] ERROR: some intrinsic memcpy() ops can copy DOWN, thus corrupting the data here */
                 memcpy((unsigned char *)ctx->tmp,
                     (unsigned char *)&(ctx->tmp[jj]),i-jj);
+#else
+                    memmove(ctx->tmp, &ctx->tmp[jj], i-jj);
+#endif
                 ctx->tmp_len=i-jj;

Some platforms (MSVC200x with intrinsic functions and maximum speed optimizations, for example) will produce a memcpy() which does not copy /upwards/ but rather /downwards/ (counting indexes down to zero, so to speak). Perfectly legal with respect to the standards; a bit of a bother in this code as it will blow your desirable data to smithereens. Ouch.



-----------------------------------
 static int b64_write(BIO *b, const char *in, int inl)
     {
-    int ret=inl,n,i;
+    int ret=0; /* [i_a] ERROR: (int)inl; will cause race condition for nonblock bio returning retry code to us below */
+    int n;
+    int i;
     BIO_B64_CTX *ctx;


was: ret=inl; as a starter;
is: ret=0; as a starter, then add each piece we processed to ret.

Sorry, 'race' is a misnomer; my English isn't always up to snuff 24/7.

The point is:
imagine the BIO_write()s in there (in the loop), being stacked on top of another BIO, and everybody happily running in NONBLOCKING mode, then /this/ code may receive 'retry' error codes from those BIO_write()s due to the nonblocky nature of the BIO chain then.
Old code will then run with ret==inl i.e. 'everything done! You can all go back to sleep now!' which is VERY WRONG when the retry happens halfway through.

Result:
Severe loss of data; corrupted stream. You're OUT.

How did that happen?
Have another look at those lines
             i=BIO_write(b->next_bio,&(ctx->buf[ctx->buf_off]),n);
             if (i <= 0)
                 {
                 BIO_copy_next_retry(b);
                 return((ret == 0)?i:ret);
                 }
especially the return statement there and given the old 'ret=inl' init, think it through.

It is much healthier to the BIO chain and caller to report the /actual/ number of bytes /processed/ than just forgetting about the remainder of the b64 write buffer when a /retry/ signal occurs. Hence the fix:
ret=0;
while (loop)
{
  do_thy_thing(chunk);
  ret += chunk;
}


The OPENSSL_assert() plethora in there has been part of the debugging effort back then (this has been an old bug, which I never got to filing before :-(( ); you may want to keep them as there's additional shite happening when someone mistakes this filter BIO for a 'bidirectional' one. But that's a whole another diff (independent read and write channel b64 enc/dec)






-------------------------                 {
Rest of path is a typo fix, introduction of the puts() member for this BIO (so one can write text strings using BIO_puts() to the BIO chain) and the

     if ((ctx == NULL) || (b->next_bio == NULL)) return(0);
 
+    BIO_clear_retry_flags(b);
+
     if (ctx->encode != B64_DECODE)

vs.

         outl-=i;
         out+=i;
         }
-    BIO_clear_retry_flags(b);
+    /* BIO_clear_retry_flags(b); */
     BIO_copy_next_retry(b);
     return((ret == 0)?ret_code:ret);
     }

is a fix for the retry flag logic, which, if I'm not mistaken, should always look like this:

read/write()
{
    BIO_clear_retry_flags(b);
    do_work() / call_next_BIO_in_chain();
    BIO_copy_next_retry(b);
    return ret;
}

instead of what a (very) few BIOs (like this one) have:


read/write()
{
    do_work() / call_next_BIO_in_chain();
    BIO_clear_retry_flags(b);
    BIO_copy_next_retry(b);
    return ret;
}

as the latter will loose any retry flagging done in the do_work() section of the implementation.



--
Met vriendelijke groeten / Best regards,

Ger Hobbelt

--------------------------------------------------
web:    http://www.hobbelt.com/
       http://www.hebbut.net/
mail:   g...@hobbelt.com
mobile: +31-6-11 120 978
--------------------------------------------------

Attachment: evp_bio_b64_c_bug1_2_feat1.diff
Description: Binary data

Reply via email to