Java 强化:vsprintf:阻止33字节写入32字节缓冲区

Java 强化:vsprintf:阻止33字节写入32字节缓冲区,java,android,c++,java-native-interface,Java,Android,C++,Java Native Interface,早上好, 我在一个Android项目上使用一个本机库,它只是包签名的一个简单md5hash,但是 我在使用JNI功能的64位设备上面临一个问题: char *getSignatureMd5(JNIEnv *env, jobject obj) { char *sign = loadSignature(env, obj); MD5_CTX context = {0}; MD5Init(&context); MD5Update(&context, (un

早上好, 我在一个Android项目上使用一个本机库,它只是包签名的一个简单md5hash,但是 我在使用JNI功能的64位设备上面临一个问题:

char *getSignatureMd5(JNIEnv *env, jobject obj) {
    char *sign = loadSignature(env, obj);
    MD5_CTX context = {0};
    MD5Init(&context);
    MD5Update(&context, (unsigned char *) sign, strlen(sign));
    unsigned char dest[16] = {0};
    MD5Final(dest, &context);
    int i;
    static char destination[32] = {0};
    for (i = 0; i < 16; i++) {
        sprintf(destination, "%s%02x", destination, dest[i]);
    }
    LOGD("MD5 Chacksum : %s", destination);
    return destination;
}
char*getSignatureMd5(JNIEnv*env,jobject对象){
char*sign=loadSignature(env,obj);
MD5_CTX上下文={0};
MD5Init(和上下文);
MD5Update(&context,(unsigned char*)符号,strlen(符号));
无符号字符dest[16]={0};
MD5Final(目的地和背景);
int i;
静态字符目标[32]={0};
对于(i=0;i<16;i++){
sprintf(目的地,“%s%02x”,目的地,目的地[i]);
}
LOGD(“MD5检查和:%s”,目的地);
返回目的地;
}
我多年来一直在使用它,因此代码本身没有任何变化,但我猜NDK或Cmake更新会导致以下情况:-( 这是我收到的坠机报告:

2020-04-18 08:38:13.038 5621-5621/? A/DEBUG: Revision: '10'
2020-04-18 08:38:13.038 5621-5621/? A/DEBUG: ABI: 'arm64'
2020-04-18 08:38:13.038 5621-5621/? A/DEBUG: pid: 5187, tid: 5423, name: APP-AsyncTa  >>> com.pecana.app <<<
2020-04-18 08:38:13.038 5621-5621/? A/DEBUG: signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
2020-04-18 08:38:13.040 5621-5621/? A/DEBUG: Abort message: 'FORTIFY: vsprintf: prevented 33-byte write into 32-byte buffer'
2020-04-18 08:38:13.040 5621-5621/? A/DEBUG:     x0  0000000000000000  x1  000000000000152f  x2  0000000000000006  x3  0000000000000008
2020-04-18 08:38:13.040 5621-5621/? A/DEBUG:     x4  0000000000000000  x5  0000000000000000  x6  0000000000000000  x7  0000000000000038
2020-04-18 08:38:13.040 5621-5621/? A/DEBUG:     x8  0000000000000083  x9  0000007007fb9878  x10 fffffff87ffffbdf  x11 0000000000000001
2020-04-18 08:38:13.041 5621-5621/? A/DEBUG:     x12 0000000000000030  x13 000000005e9aa050  x14 001fb5a729ce1580  x15 000040d9dceb878c
2020-04-18 08:38:13.042 5621-5621/? A/DEBUG:     x16 0000007007ff02a0  x17 0000007007f2f920  x18 0000000000000000  x19 0000000000001443
2020-04-18 08:38:13.042 5621-5621/? A/DEBUG:     x20 000000000000152f  x21 0000000000000083  x22 000000000000000f  x23 0000006f6a48dcc2
2020-04-18 08:38:13.043 5621-5621/? A/DEBUG:     x24 0000000000000004  x25 0000006f5c0f5588  x26 0000006f6b0a04a0  x27 0000000000000001
2020-04-18 08:38:13.043 5621-5621/? A/DEBUG:     x28 0000006f5c0f30c0  x29 0000006f5c0f2c00
2020-04-18 08:38:13.044 5621-5621/? A/DEBUG:     sp  0000006f5c0f2bc0  lr  0000007007f22d68  pc  0000007007f22d94
2020-04-18 08:38:13.038 5621-5621/?A/DEBUG:Revision:'10'
2020-04-18 08:38:13.038 5621-5621/?A/DEBUG:ABI:'arm64'
2020-04-18 08:38:13.038 5621-5621/?A/DEBUG:pid:5187,tid:5423,name:APP AsyncTa>>>com.pecana.APP
static char destination[33]={0};//为空终止符再添加一个字节
char*dstptr=destination;//指向静态缓冲区第一个字符的指针
对于(i=0;i<16;i++){
sprintf(dstptr,“%02x”,dest[i]&0xFF);//覆盖缓冲区中的两个字符
dstptr+=2;//前进指针
}
这样,它将每次覆盖32个字符,而不关心旧内容

请注意,这似乎是Android代码的一些支持功能,如果两个线程同时调用它,则使用相同的静态缓冲区来生成结果,因此您可能会在两个答案中得到损坏的答案(一个调用的部分答案与第二个调用的部分答案混合)。因此,这不是线程安全的

另外,对该函数的下一次调用将使以前的版本无效(如果您只存储指向结果的指针,而没有将其复制到Java字符串中)

我可能会建议返回新实例化的jstring,以供顶级Android代码直接使用(另外,它可能是线程安全的,因为代码中的所有其他实例都是局部变量,只有
destination
是静态的,因此如果MD5函数本身是线程安全的,并且您去掉了
destination
,那么整个函数都将是线程安全的)不要添加源例子,因为JNI对我来说是一个可怕的痛苦,杀死C++的所有好处,并促进所有的坏,很容易出错和丢失类型等等。(我个人的有害偏见是——如果你不想讨论的话,请忽略它——它是故意这样设计的,目的是避免过多的C/C++与Java一起使用,但话说回来,它是这么多年前在C++11之前完成的,所以也许只是设计Java的人是无知的……实际上,考虑到整个Ja,这更有意义。)va语言)

静态字符目标[33]={0};//为空终止符再添加一个字节
char*dstptr=destination;//指向静态缓冲区第一个字符的指针
对于(i=0;i<16;i++){
sprintf(dstptr,“%02x”,dest[i]&0xFF);//覆盖缓冲区中的两个字符
dstptr+=2;//前进指针
}
这样,它将每次覆盖32个字符,而不关心旧内容

请注意,这似乎是Android代码的一些支持功能,如果两个线程同时调用它,则使用相同的静态缓冲区来生成结果,因此您可能会在两个答案中得到损坏的答案(一个调用的部分答案与第二个调用的部分答案混合)。因此,这不是线程安全的

另外,对该函数的下一次调用将使以前的版本无效(如果您只存储指向结果的指针,而没有将其复制到Java字符串中)


我可能会建议返回新实例化的jstring,以供顶级Android代码直接使用(另外,它可能是线程安全的,因为代码中的所有其他实例都是局部变量,只有
destination
是静态的,因此如果MD5函数本身是线程安全的,并且您去掉了
destination
,那么整个函数都将是线程安全的)不要添加源例子,因为JNI对我来说是一个可怕的痛苦,杀死C++的所有好处,并促进所有的坏,很容易出错和丢失类型等等。(我个人的有害偏见是——如果你不想讨论的话,请忽略它——它是故意这样设计的,目的是避免过多的C/C++与Java一起使用,但话说回来,它是这么多年前在C++11之前完成的,所以也许只是设计Java的人是无知的……实际上,考虑到整个Ja,这更有意义。)(va语言)

您正在向缓冲区写入32个字节,外加1个字节用于您忘记的空终止符,因此请将其设置为
destination[33]
,而不是
destination[32]
我多年来一直在使用它。
sprintf(destination,%s%02x),destination,dest[I])一个非常奇怪的构造。你把
目的地
设为静态的,所以它只会被初始化为
{0}
一次。为什么你甚至需要
sprintf
来创建一个固定大小的十六进制字符串呢?有多种方法可以做到这一点。例如,
静态常量字符[]=“0123456789ABCDEF”;对于(int i=0;i<16;++i){destination[i*2+0]=数字[(dest[i]>>4)&0x0F];destination[i*2+1]=数字[dest[i]&0x0F];}
您正在向缓冲区写入32个字节加上1个字节作为您忘记的空终止符,因此将其设置为
destination[33]
[32]
我从几年前就开始使用它了
。这些年来一直有缓冲区溢出。
sprintf(d
    static char destination[33] = {0};  // add one more byte for null-terminator
    char* dstptr = destination;         // pointer to first char of the static buffer
    for (i = 0; i < 16; i++) {
        sprintf(dstptr, "%02x", dest[i]&0xFF);  // overwrite two chars in buffer
        dstptr += 2;                    // advance pointer
    }