Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
bgColor#ffcccc
langc
#include <stdatomic.h>
 
/* Sets index to point to index of maximum element in array
   and value to contain maximum array value. */
void find_max_element(_Atomic int array[], size_t index, int value);

/* ... */

void func(void) {
  _Atomic int array[];
  size_t index;
  int value;
  find_max_element( array, &index, &value);
  /* ... */
  if (!atomic_compare_exchange_weak( array[index], value, 0)) {
    /* Handle error */
  }
}

The compare-and-swap operation will set array[index] to 0 if and only if it is currently set to value. However, this does not necessarily zero out the maximum value of the array because:

...

Code Block
bgColor#ccccff
langc
#include <stdatomic.h>
#include <threads.h>
 
void func(void) {
  _Atomic int array[];
  size_t index;
  int value;
  mtx_t array_mutex;
  int result;
  if ((result = mtx_lock(&array_mutex)) == thrd_error) {
    /* Handle error */
  }
  find_max_element( array, &index, &value);
  /* ... */
  if (!atomic_compare_exchange_weak( array[index], value, 0)) {
    /* Handle error */
  }
  if ((result = mtx_unlock(&array_mutex)) == thrd_error) {
    /* Handle error */
  }
}

Noncompliant Code Example (GNU Glib)

...

Code Block
bgColor#FFcccc
langc
#include <glib.h>
#include <glib-object.h>

typedef struct node_s {
  void *data;
  Node *next;
} Node;

typedef struct queue_s {
  Node *head;
  Node *tail;
} Queue;

Queue* queue_new(void) {
  Queue *q = g_slice_new( sizeof(Queue));
  q->head = q->tail = g_slice_new0new( sizeof(Node));
  return q;
}

void queue_enqueue(Queue *q, gpointer data) {
  Node *node;
  Node *tail;
  Node *next;

  node = g_slice_new(Node);
  node->data = data;
  node->next = NULL;
  while (TRUE) {
    tail = q->tail;
    next = tail->next;
    if (tail != q->tail) {
      continue;
    }
    if (next != NULL) {
      CAS(&q->tail, tail, next);
      continue;
    }
    if (CAS( &tail->next, null, node)) {
      break;
    }
  }
  CAS(&q->tail, tail, node);
}

gpointer queue_dequeue(Queue *q) {
  Node *node;
  Node *tail;
  Node *next;

  while (TRUE) {
    head = q->head;
    tail = q->tail;
    next = head->next;
    if (head != q->head) {
      continue;
    }
    if (next == NULL) {
      return NULL; //* Empty */
    }
    if (head == tail) {
      CAS(&q->tail, tail, next);
      continue;
    }
    data = next->data;
    if (CAS(&q->head, head, next)) {
      break;
    }
  }
  g_slice_free(Node, head);
  return data;
}

...

Code Block
bgColor#ccccff
langc
// Hazard pointers types and structure
 structure HPRecType {
   HP[K]:*Nodetype;
  Next:*HPRecType;
}
 
// The header of the HPRec list
 HeadHPRec: *HPRecType;
// Per-thread private variables
 rlist: listType; // initially empty
 rcount: integer; // initially 0


// The retired node routine
RetiredNode(node:*NodeType) {
  rlist.push(node);
  rcount++;
  if(rcount >= R)
    Scan(HeadHPRec);
}


// The scan routine
Scan(head:*HPRecType) {
  // Stage1: Scan HP list and insert non-null values in plist
  plist.init();
  hprec<-head;
  while (hprec != null) {
    for (i<-0 to K-1) {
      hptr<-hprec^HP[i];
      if (hptr!= null)
        plist.insert(hptr);
    }
    hprec<-hprec^Next;
  }

  // Stage 2: search plist
  tmplist<-rlist.popAll();
  rcount<-0;
  node<-tmplist.pop();
  while (node != null) {
    if (plist.lookup(node)) {
      rlist.push(node);
      rcount++;
    }
    else {
      PrepareForReuse(node);
    }
    node<-tmplist.pop();
  }
  plist.free();
}

...

Code Block
bgColor#ccccff
langc
#include <glib.h>
#include <glib-object.h>
 
void queue_enqueue(Queue *q, gpointer data) {
  Node *node;
  Node *tail;
  Node *next;

  node = g_slice_new(Node);
  node->data = data;
  node->next = NULL;
  while (TRUE) {
    tail = q->tail;
    HAZARD_SET(0, tail);      
      HAZARD_SET(0, tail);  //* Mark tail as hazardous */
    if (tail != q->tail) {            ///* Check tail hasn't changed */
      continue;
    }
    next = tail->next;
    if (tail != q->tail) {
      continue;
    }
    if (next != NULL) {
      CAS(&q->tail, tail, next);
      continue;
    }
    if (CAS(&tail->next, null, node) {
      break;
    }
  }
  CAS(&q->tail, tail, node);
}


gpointer queue_dequeue(Queue *q) {
  Node *node;
  Node *tail;
  Node *next;

  while (TRUE) {
    head = q->head;
    LF_HAZARD_SET(0, head);    //* Mark head as hazardous. */
    if (head != q->head) {     //* Check head hasn't changed. */
      continue;
    }
    tail = q->tail;
    next = head->next;
    LF_HAZARD_SET(1, next);    //* Mark next as hazardous. */
    if (head != q->head) {
      continue;
    }
    if (next == NULL) {
      return NULL; //* Empty */
    }
    if (head == tail) {
      CAS(&q->tail, tail, next);
      continue;
    }
    data = next->data;
    if (CAS(&q->head, head, next)) {
      break;
    }
  }
  LF_HAZARD_UNSET(head);   );     //* Retire head, and perform
                                // reclamation if needed. */
  return data;
}

Compliant Solution (GNU Glib, Mutex)

...

Code Block
bgColor#ccccff
langc
#include <glib-object.h>
#include <threads.h>

typedef struct node_s {
  void *data;
  Node *next;
} Node;

typedef struct queue_s {
  Node *head;
  Node *tail;
  mtx_t mutex;
} Queue;

Queue* queue_new(void) {
  Queue *q = g_slice_new( sizeof(Queue));
  q->head = q->tail = g_slice_new0new( sizeof(Node));
  return q;
}

int queue_enqueue(Queue *q, gpointer data) {
  Node *node;
  Node *tail;
  Node *next;

  /* Lock the queue before accessing the contents and
     check the return code for success. */
  if(mtx_lock(&(q->mutex)) != thrd_success) {
    return -1;  /* Indicate failure */
  }
  else {
    node = g_slice_new(Node);
    node->data = data;
    node->next = NULL;

    if(q->head == NULL) {
      q->head = node;
      q->tail = node;
    }
    else {
      q->tail->next = node;
      q->tail = node;
    }
    /* Unlock the mutex and chech the return code. */
    if (mtx_unlock(&(queue->mutex)) != thrd_success) {
     return -1;  /* Indicate failure */
    }
  }
  return 0;
}

gpointer queue_dequeue(Queue *q) {
  Node *node;
  Node *tail;
  Node *next;

  if (mtx_lock(&(q->mutex)) != thrd_success) {
    return NULL;        /* Indicate failure */
  }
  else {
    head = q->head;
    tail = q->tail;
    next = head->next;
    data = next->data;
    q->head = next;
    g_slice_free(Node, head);
    if (mtx_unlock(&(queue->mutex)) != thrd_success ) {
      return NULL;      /* Indicate failure */
    }
  }
  return data;
}

...

The likelihood of having a race condition is low. Once the race condition occurs, the reading memory that has already been freed can lead to abnormal program termination or unintended information disclosure.

GuidelineRule

Severity

Likelihood

Remediation Cost

Priority

Level

CON39-C

Medium

unlikely

High

P2

L3

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Bibliography

 

...