【问题标题】:C threads corrupting each otherC线程互相破坏
【发布时间】:2021-08-20 03:30:20
【问题描述】:

所以我有一个奇怪的问题,我不太明白为什么会这样。在 md4checker 中,我启动 n 个 pthread 来获取并检查 MD4 哈希。在 md4.c 中,我生成一个 MD4 哈希。如果我将 n 个线程设置为 1,它可以完美运行。它以完美的准确性生成 MD4 哈希(我在循环中运行了 1,000,000 次尝试,但没有一次失败)。但是,当我使用 n 个线程作为 2(或更高)运行相同的代码时,它会随机失败很多。

md4.c 文件是我在网上找到的另一个文件的派生文件,但我对其进行了一些调整,因为 original md4.c 存在内存泄漏(并且运行 50,000,000+ 哈希使泄漏在大约 15 分钟内填满了 16GB 的 RAM)。如果这只是它不起作用的问题,我会知道从哪里开始,但我真的不知道多个线程在哪里以及为什么在这里相互破坏。

编辑:如果我在 md4checker.c 中将 usleep(100) 添加到工作线程,它会将失败率降低到正常情况的 10%。

md4checker.c(只运行一个时有效):

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/sysinfo.h>

#include "md4.c"

struct data{
    char hash[33];
    int done;
};

void *worker(void *ptr) {
    int count=0;
    char hash[33];
    strcpy(hash, ((struct data *)ptr)->hash);
    hash[32] ='\0';
    char *md4;
    int fails =0;

    int runs =1000;
    while(count < runs) {
        md4 = MD4("cbff7", 5);
        if(strcmp(md4, hash) != 0) {
            ++fails;
        }
        free(md4);
        count++;
    }
    ((struct data *)ptr)->done = 1;
    printf("Done. Failed %d/%d times.\n", fails, runs);
}

void runprocs(int procs) {
    printf("Running process on %d thread(s)\n", procs);

    struct data d ={
        .hash = "4e0d289576880188d4b968fe626bccef\0",
        .done =0
    };

    pthread_t threads[procs];
    void *ptr =&d;

    for(int i=0; i<procs; ++i) {
        int rc = pthread_create(&threads[i], NULL, worker, ptr);
    }
    while (!d.done) {
        usleep(10000);
    }
}

int main(int argc, char *argv[]) {
    if (argc < 2) return -1;
    runprocs(1);
    runprocs(2);
    runprocs(4);
}

运行四次后,我得到的输出是:

运行一个:

Running process on 1 thread(s)
Done. Failed 0/1000 times.
Running process on 2 thread(s)
Done. Failed 490/1000 times.
Done. Failed 489/1000 times.
Running process on 4 thread(s)
Done. Failed 941/1000 times.
Done. Failed 883/1000 times.
Done. Failed 847/1000 times.
Done. Failed 473/1000 times.

运行两个:

Running process on 1 thread(s)
Done. Failed 0/1000 times.
Running process on 2 thread(s)
Done. Failed 19/1000 times.
Done. Failed 17/1000 times.
Running process on 4 thread(s)
Done. Failed 953/1000 times.
Done. Failed 891/1000 times.
Done. Failed 884/1000 times.
Done. Failed 850/1000 times.

运行三:

Running process on 1 thread(s)
Done. Failed 0/1000 times.
Running process on 2 thread(s)
Done. Failed 431/1000 times.
Done. Failed 371/1000 times.
Running process on 4 thread(s)
Done. Failed 931/1000 times.
Done. Failed 928/1000 times.
Done. Failed 720/1000 times.
Done. Failed 703/1000 times.

跑四:

Running process on 1 thread(s)
Done. Failed 0/1000 times.
Running process on 2 thread(s)
Done. Failed 82/1000 times.
Done. Failed 84/1000 times.
Running process on 4 thread(s)
Done. Failed 909/1000 times.
Done. Failed 928/1000 times.
Done. Failed 790/1000 times.
Done. Failed 808/1000 times.

每组的第一行都是完美的(从主线程完成)。然后它在两个新线程中运行 1,000 次,它们都打印失败/运行结果(如您在上面的代码中所见)。那么为什么随机数失败呢?我在这里很困惑,大声笑。任何帮助将不胜感激。

md4.c:

#include <stdlib.h>
#include <string.h>
#include <stdint.h>

char *MD4(char *, int); //this is the prototype you want to call. Everything else is internal.


static uint32_t *MD4Digest(uint32_t *w, int len);
static void setMD4Registers(uint32_t, uint32_t, uint32_t, uint32_t);
static uint32_t changeEndianness(uint32_t);
static void resetMD4Registers(void);
static uint32_t stringToUint32(char *);

static const char *BASE16 = "0123456789abcdef=";

#define F(X,Y,Z) (((X)&(Y))|((~(X))&(Z)))
#define G(X,Y,Z) (((X)&(Y))|((X)&(Z))|((Y)&(Z)))
#define H(X,Y,Z) ((X)^(Y)^(Z))

#define LEFTROTATE(A,N) ((A)<<(N))|((A)>>(32-(N)))

#define MD4ROUND1(a,b,c,d,x,s) a += F(b,c,d) + x; a = LEFTROTATE(a, s);
#define MD4ROUND2(a,b,c,d,x,s) a += G(b,c,d) + x + (uint32_t)0x5A827999; a = LEFTROTATE(a, s);
#define MD4ROUND3(a,b,c,d,x,s) a += H(b,c,d) + x + (uint32_t)0x6ED9EBA1; a = LEFTROTATE(a, s);

static uint32_t A = 0x67452301;
static uint32_t B = 0xefcdab89;
static uint32_t C = 0x98badcfe;
static uint32_t D = 0x10325476;

void Concat(char **out, int olen, char* second, int slen) {
    
    if(*out == NULL ) {
        *out = malloc(1);
        *out[1] = '\0';
    }

    char *old = *out;           // Grab the original string.
    //int len = (sizeof(char)*((strlen(old)+strlen(second)+1)));    // Get the length of the combined strings plus 1 for \0

    *out = malloc(olen+slen+1);     // Create the new char array to hold the combined strings.
    memset(*out, 0, olen+slen+1);   // Set all bits to zero in new array.

    char *p = *out;                 // We'll use p to track position for writing the values.
    //strcpy(p, old);                   // Copy the original string to p;
    memcpy(p, old, olen);

    p += olen;                      // Move p forward by the length of old.
    //strcpy(p, second);                // Copy the second string to p
    memcpy(p, second, slen);

    free(old);                      // Free old to prevent memory leak.
    free(second);   
}

int Expand(char **out, int amt) {
    int len = strlen(*out)+amt;         // Get the length of the array + expand amount \0

    char *new;                          // Create a new pointer.
    new = malloc(sizeof(char)*len);     // Create the new char array
    memset(new, 0, sizeof(char)*len);   // Set all bits to zero in new array.

    strcpy(new, *out);                  // Copy the original string to new array;
    free(*out);                         // Free the original memory to prevent leak
    *out = new;

    return len;                         // Return the new memory size
}

char *base16Encode(char *in, int len){
    char *out = malloc(len*2);
    int i,j;

    j=0;
    for(i=0; i<len; i++){
        out[j++]=BASE16[((in[i] & 0xF0)>>4)];
        out[j++]=BASE16[(in[i] & 0x0F)];
    }
    out[j]='\0';
    free(in);
    return out;
}

uint32_t stringToUint32(char *c){
    uint32_t l;
    int i;
    l=0;
    for(i=0; i<4; i++){
        l = l|(((uint32_t)((unsigned char)c[i]))<<(8*(3-i)));
    }
    return l;
}

char *uint32ToString(uint32_t l){
    char *c = malloc(sizeof(uint32_t)+1);
    memset(c, 0, sizeof(uint32_t)+1);

    int i;
    for(i=0; i<4; i++){
        c[i] = (l >> (8*(3-i))) & 0xFF;
    }
    return c;
}

char *MD4(char *str, int temporaryvar){

    uint64_t mlen=strlen(str);          // Get the length of str + 1 for \0
    uint64_t slen=mlen;
    char *m = malloc(mlen+1);           // Create a pointer to manipulate data and give it an array of size mlen
    strcpy(m, str);                     // Copy str to m
    m[mlen] = '\0';                     // Set the last value to 0.

    unsigned char *oneByte = malloc(sizeof(char));
    oneByte[0] = 0x80;
    Concat(&m, mlen, oneByte, 1);       // Add the 1 byte.
    int i, wlen;

    mlen=strlen(m);

    i=((56-mlen)%64);
    if(i<0) i+=64;

    mlen = Expand(&m, i);

    uint32_t *w = malloc(sizeof(uint32_t)*(mlen/4+2));

    for(i=0; i<mlen/4; i++){
        w[i]=stringToUint32(m+(4*i));
    }
    w[i++] = (slen<<3) & 0xFFFFFFFF;
    w[i++] = (slen>>29) & 0xFFFFFFFF;

    wlen=i;
    
    for(i=0; i<wlen-2; ++i){
        w[i]=changeEndianness(w[i]);
    }

    uint32_t *hash = MD4Digest(w,wlen);
    
    char *digest = malloc(1);
    memset(digest, 0, 1);
    
    //digest=newString(NULL,0);
    for(i=0; i<4; i++){
        hash[i]=changeEndianness(hash[i]);
        Concat(&digest, sizeof(uint32_t)*i,uint32ToString(hash[i]), sizeof(uint32_t));
    }


    // Don't forget to free up your memory.
    free(m);
    free(w);
    free(hash);

    return base16Encode(digest, sizeof(uint32_t)*4);
}

uint32_t *MD4Digest(uint32_t *w, int len){
    //assumes message.len is a multiple of 64 bytes.
    int i,j;
    uint32_t X[16];
    uint32_t *digest = malloc(sizeof(uint32_t)*4);
    uint32_t AA, BB, CC, DD;
    
    for(i=0; i<len/16; i++){
        for(j=0; j<16; j++){
            X[j]=w[i*16+j];
        }

        AA=A;
        BB=B;
        CC=C;
        DD=D;

        MD4ROUND1(A,B,C,D,X[0],3);
        MD4ROUND1(D,A,B,C,X[1],7);
        MD4ROUND1(C,D,A,B,X[2],11);
        MD4ROUND1(B,C,D,A,X[3],19);
        MD4ROUND1(A,B,C,D,X[4],3);
        MD4ROUND1(D,A,B,C,X[5],7);
        MD4ROUND1(C,D,A,B,X[6],11);
        MD4ROUND1(B,C,D,A,X[7],19);
        MD4ROUND1(A,B,C,D,X[8],3);
        MD4ROUND1(D,A,B,C,X[9],7);
        MD4ROUND1(C,D,A,B,X[10],11);
        MD4ROUND1(B,C,D,A,X[11],19);
        MD4ROUND1(A,B,C,D,X[12],3);
        MD4ROUND1(D,A,B,C,X[13],7);
        MD4ROUND1(C,D,A,B,X[14],11);
        MD4ROUND1(B,C,D,A,X[15],19);

        MD4ROUND2(A,B,C,D,X[0],3);
        MD4ROUND2(D,A,B,C,X[4],5);
        MD4ROUND2(C,D,A,B,X[8],9);
        MD4ROUND2(B,C,D,A,X[12],13);
        MD4ROUND2(A,B,C,D,X[1],3);
        MD4ROUND2(D,A,B,C,X[5],5);
        MD4ROUND2(C,D,A,B,X[9],9);
        MD4ROUND2(B,C,D,A,X[13],13);
        MD4ROUND2(A,B,C,D,X[2],3);
        MD4ROUND2(D,A,B,C,X[6],5);
        MD4ROUND2(C,D,A,B,X[10],9);
        MD4ROUND2(B,C,D,A,X[14],13);
        MD4ROUND2(A,B,C,D,X[3],3);
        MD4ROUND2(D,A,B,C,X[7],5);
        MD4ROUND2(C,D,A,B,X[11],9);
        MD4ROUND2(B,C,D,A,X[15],13);

        MD4ROUND3(A,B,C,D,X[0],3);
        MD4ROUND3(D,A,B,C,X[8],9);
        MD4ROUND3(C,D,A,B,X[4],11);
        MD4ROUND3(B,C,D,A,X[12],15);
        MD4ROUND3(A,B,C,D,X[2],3);
        MD4ROUND3(D,A,B,C,X[10],9);
        MD4ROUND3(C,D,A,B,X[6],11);
        MD4ROUND3(B,C,D,A,X[14],15);
        MD4ROUND3(A,B,C,D,X[1],3);
        MD4ROUND3(D,A,B,C,X[9],9);
        MD4ROUND3(C,D,A,B,X[5],11);
        MD4ROUND3(B,C,D,A,X[13],15);
        MD4ROUND3(A,B,C,D,X[3],3);
        MD4ROUND3(D,A,B,C,X[11],9);
        MD4ROUND3(C,D,A,B,X[7],11);
        MD4ROUND3(B,C,D,A,X[15],15);

        A+=AA;
        B+=BB;
        C+=CC;
        D+=DD;
    }

    digest[0]=A;
    digest[1]=B;
    digest[2]=C;
    digest[3]=D;
    resetMD4Registers();
    return digest;
}

uint32_t changeEndianness(uint32_t x){
    return ((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | ((x & 0xFF0000) >> 8) | ((x & 0xFF000000) >> 24);
}

void setMD4Registers(uint32_t AA, uint32_t BB, uint32_t CC, uint32_t DD){
    A=AA;
    B=BB;
    C=CC;
    D=DD;
}

void resetMD4Registers(void){
    setMD4Registers(0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476);
}

【问题讨论】:

  • md4.c 具有由其函数修改的全局状态。这意味着它不是线程安全的。
  • OT:包含 C 文件是非常糟糕的做法。您应该只包含头文件,单独编译并链接它们。
  • MD4 被发现不安全over 25 years ago。你为什么用它?
  • @user207421 实际上,这就是这个程序的全部意义所在。这是一个暴力破解 MD4 哈希的案例研究。整个源代码包含一个文件,该文件生成从密钥字符串(即“abcd”到 aaa、aab、aac、aad、aba、abc 等)的每个可能的字符组合。它跨 n 个线程执行此操作(即线程一执行 aaa、aac、aba、abc,线程二执行 aab、aad、abb、abd)。这允许它每分钟解密大约 150 万个组合。这是一个学习项目。

标签: c multithreading


【解决方案1】:

那么为什么随机数失败呢?

提供的 MD4 代码不是线程安全的,您自己添加了一点线程不安全。

在文件md4.c 中特别观察变量A、B、C 和D。这些是在文件范围内声明的,没有_Thread_local 限定符,因此它们具有静态存储持续时间并由进程中的所有线程共享。这些在计算过程中被修改,所以你有涉及所有这些的数据竞争。产生的行为是未定义的,不难想象如果多个线程正在破坏彼此写入这些变量中的值,它会如何搞砸事情。

至于你自己的代码,每次调用runprocs(),主线程和每个新创建的都共享同一个struct data对象,线程读取和修改以及主线程读取,所有这些都没有同步。这也会导致未定义的行为,尽管看起来这可以通过使用互斥锁或其他同步机制来解决。


此外,MD4 代码似乎是确定性的——给定相同的输入,它总是(如果运行单线程以避免未定义的行为)产生相同的输出。因此,不清楚您希望通过在同一输入上的多个线程中运行它来实现什么。

此外,while(!d.done) 循环毫无意义且形式不佳。您应该通过pthread_join() 加入每个线程以在其之后清理其资源,并且由于这具有等待线程终止的(主要)效果,您不需要也滚动自己的等待终止。

【讨论】:

  • 我将不得不尝试调整您提到的内容。不过,你说得对,它是确定性的。看,我有一个更大的文件运行相同的 MD4() 进程,但字符串可能会改变。我创建了 md4checker.c 来尝试限制可能无法看到正在发生的事情的区域。这是我在 C 语言中的第一个多线程程序(我在 python、C# 和 VB.net 中完成过)。
  • 我修复了静态的 A、B、C、D(它们是原始 md4.c 的残余)并将我的循环从 while (!d.done) 切换到 for (i=0; i
猜你喜欢
  • 1970-01-01
  • 2020-01-25
  • 2021-11-07
  • 2011-07-21
  • 2015-04-16
  • 1970-01-01
  • 2023-03-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多