Search code examples
cpointersstm32lwip

Member of a structure gets assigned a new address after freeing the structure


I am working on a program that uses lwIP library and turns STM32F769I-DISCO into a TCP client that connects to a TCP server (this role is being fulfilled by Hercules Setup Utility as for now). I made a simple function tcpClientCloseConnection() that frees a custom structure tcpClientStruct and TCP protocol control block (tcp_pcb structure from lwIP) when the TCP connection is supposed to be closed. It frees tcp_pcb inside of tcpClientStruct first (tcp_close() function frees it), then tcpClientStruct.

The problem is, when tcpClientStruct is freed, tcpClientStruct->pcb is no longer NULL and its address changes from 0x0. Because of that, when tcpClientCloseConnection() is invoked again, it returns false when it checks if tcp_pcb is NULL and tries to close it, causing the program to end up in HardFault_Handler() function.

I could check if my custom structure is NULL and not try to free tcp_pcb inside of it if it isn't, but shouldn't tcp_pcb be NULL anyway? I am confused by the way my program behaves and I don't know what could be the reason behind it. I'll include initialising functions in case the problem's cause could be inside of them.

Thank you for help and suggestions in advance!

//main.c

//...

#define SERVER_PORT (33333)
#define SERVER_IP ("192.168.0.103")
ip_addr_t serverIP;
tcpClientStruct *tcpClientInfo=NULL;

//...

int main(void) {
  
  //...

  ipaddr_aton(SERVER_IP, &serverIP);

  //...

  while(1) {
    //...
    uint8_t conn=verifyConnectionProblems(&tcpClientInfo, SERVER_PORT, serverIP);
    //...
  }
}
//tcpClient.h

typedef struct {
  uint8_t state; //current connection state
  uint8_t retries;
  struct tcp_pcb *pcb; //pointer to the current tcp_pcb
  struct pbuf *packetBuffer; //pointer to received/to be transmitted data
} tcpClientStruct;

//...
//tcpClient.c

int8_t verifyConnectionProblems(tcpClientStruct **tcpClientInfo, uint16_t serverPort, ip_addr_t serverIP) {
  
  //...
  
  tcpClientCloseConnection(&((*tcpClientInfo)->pcb), tcpClientInfo);
  
  //...
}


void tcpClientCloseConnection(struct tcp_pcb **tcpPCB, tcpClientStruct **clientStr) {

  if (tcpPCB!=NULL && *tcpPCB!=NULL) {
    tcp_close(*tcpPCB);
    *tcpPCB=NULL;
  }
  if (clientStr!=NULL && *clientStr!=NULL) { //free clientStruct
    if ((*clientStr)->packetBuffer!=NULL) {
      mem_free((*clientStr)->packetBuffer);
      (*clientStr)->packetBuffer=NULL;
    }
    asm("NOP"); //tcpClientInfo addr=0x20005d24 <ram_heap+8>, tcpClientInfo->pcb addr=0x0
    mem_free(*clientStr);
    asm("NOP"); //tcpClientInfo addr=0x20005d24 <ram_heap+8>, tcpClientInfo->pcb addr=0x0
    *clientStr=NULL;
    asm("NOP"); //tcpClientInfo addr=0, tcpClientInfo->pcb 0xf73d6e6a
  }
}




//initialising functions
uint8_t serverDisconnected=0;

int8_t tcpPCBReinit(struct tcp_pcb **tcpPCB) {
    if (tcpPCB!=NULL && *tcpPCB!=NULL) {
        mem_free(*tcpPCB);
        *tcpPCB=NULL;
    }
    *tcpPCB=tcp_new(); //create new TCP protocol control block
    if (*tcpPCB==NULL) {
        return ERR_MEM;
    }

    return ERR_OK;
}

int8_t tcpClientStructReinit(tcpClientStruct **clientStr) {
    if (*clientStr!=NULL) {
        if ((*clientStr)->packetBuffer!=NULL) {
            mem_free((*clientStr)->packetBuffer);
            (*clientStr)->packetBuffer=NULL;
        }
        mem_free(*clientStr);
        *clientStr=NULL;
    }

    *clientStr=(tcpClientStruct *)mem_malloc(sizeof(tcpClientStruct));
    if (*clientStr==NULL) {
        return ERR_MEM;
    } else {
        tcpPCBReinit(&(*clientStr)->pcb);
        if ((*clientStr)->pcb==NULL) {
            mem_free(*clientStr);
            *clientStr = NULL;
            return ERR_MEM;
        }
        (*clientStr)->state=CLIENT_STATUS_NONE;
        (*clientStr)->retries=0;
        (*clientStr)->packetBuffer=NULL;
        serverDisconnected=0;
    }

    return ERR_OK;
}

int8_t tcpClientInit(const ip_addr_t *ipAddr, uint16_t port, tcpClientStruct **clientStr) {

    int8_t errorClientStrReinit=tcpClientStructReinit(clientStr);
    if (errorClientStrReinit!=ERR_OK) {
        return errorClientStrReinit;
    }

    tcp_arg((*clientStr)->pcb, (*clientStr)); //specifies the argument that should be passed to all other callback functions
    tcp_err((*clientStr)->pcb, tcpClientErrorCallback);

    //connect to the server
    int8_t connected=tcp_connect((*clientStr)->pcb, ipAddr, port, tcpClientConnectedCallback); //connect to host and invoke callback once connected successfully

    if (connected!=ERR_OK) {
        serverDisconnected=1;
    }
    else {
        serverDisconnected=0;
    }

    return connected;
}

//rest of functions...


Solution

  • I think the problem is that verifyConnectionProblems can sometimes call tcpClientCloseConnection with an invalid but non-null pointer value in the first argument (for the tcpPCB parameter).

    verifyConnectionProblems is being called in a loop:

      while(1) {
        //...
        uint8_t conn=verifyConnectionProblems(&tcpClientInfo, SERVER_PORT, serverIP);
        //...
      }
    

    verifyConnectionProblems can call tcpClientCloseConnection:

      tcpClientCloseConnection(&((*tcpClientInfo)->pcb), tcpClientInfo);
    

    OP has confirmed that verifyConnectionProblems does not check the value of *tcpClientInfo before the above call to tcpClientCloseConnection.

    Assuming the connection has not been closed yet, the call to tcpClientCloseConnection will set *tcpClientInfo to NULL via parameter clientStr (the second parameter of tcpClientCloseConnection):

        mem_free(*clientStr);
        *clientStr=NULL;
    

    The next time verifyConnectionProblems is called, it may call tcpClientCloseConnection client again:

      tcpClientCloseConnection(&((*tcpClientInfo)->pcb), tcpClientInfo);
    

    If *tcpClientInfo is still NULL, the first argument of the call to tcpClientCloseConnection given by the expression &((*tcpClientInfo)->pcb) is an invalid pointer value since it is based on a null pointer value. Since the pcb member is at a non-zero offset from the start of the type tcpClientStruct, typically the pointer value will be invalid but non-null. The first argument is passed to the first parameter tcpPCB.

    tcpClientCloseConnection does some validity checking of the parameter tcpPCB:

      if (tcpPCB!=NULL && *tcpPCB!=NULL) {
    

    If tcpPCB has an invalid but non-null value, the first part of the test (tcpPCB!=NULL) will be true, but the second part of the test (*tcpPCB!=NULL) will dereference an invalid pointer, resulting in undefined behavior.

    We do not know what other tests verifyConnectionProblems does before deciding to call tcpClientCloseConnection, but it ought to at least avoid calling it with invalid, non-null pointers. In particular, it ought to check that *tcpClientInfo is a non-null pointer value.