1

I am getting a weird segmentation fault when accessing a structure inside of a opaque structure form it's definition file. I have just recently learned about opaque pointers, but my my guess is, I am doing something wrong with the allocation of the internal structure. However, in a first call to the structure it seems to work and the it get unaccessible within the same method.

So I define my opaque structure pointer inside of connection.h:

#ifndef _Connection_h
#deifne _Connection_h
// Connection opaque pointer
typedef struct connection;
// API
_Bool connection_connect(struct connection *self);
#endif // _Connection_h

And then inside of connection.c I am allocating the internal serv structure pointer of type struct sockaddr_un.

#include "../connection.h"

struct connection {
  _Bool (*connect)();
  char *(*get_name)();
  char name[MAX_NAMELEN];
  char hostname[MAX_NAMELEN];
  uint serv_id;
  struct sockaddr_un *serv; // Server socket structure
};

// ** Public Interface **
_Bool connection_connect(struct connection *self) {
  printf("Generic connection (opaque pointer)..\n");
  strcpy(self->name, SERVERNAME);
  // Socket path
  char path[108];
  strcpy(path, SERVERNAME);
  strcat(path, "_socket");
  printf("self->name = %s\n", self->name);
  // Allocate the serv socket structure
  self->serv = malloc(sizeof(*self->serv));
  strcpy(self->serv->sun_path, path);
  self->serv->sun_family = AF_UNIX;
  printf("self->serv->sun_path = %s\n", self->serv->sun_path);
  printf("self->serv->sun_family = %hn\n", self->serv->sun_family);
  
  // Locate the host
  char hostname[MAX_NAMELEN];
  gethostname(hostname, MAX_NAMELEN);
  strcpy(self->hostname, hostname);

  if ((self->serv_id = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
    handle_error("Socket");
  return 1;
}

Then allocation of the connection interface methods is handled by client. For the minimal reproducible example a client type implementation may handle interface methods allocations like so:

#include "../connection.h"

typedef struct connection {
  void (*connect)();
} *client;

void client_connect(client self) {
  connection_connect(self);
  printf("Client connection..\n");
}

client make_client(client self) {
  client tmp = malloc(sizeof(client));
  tmp->connect = client_connect;
  return tmp;
}

// Main procedure
int main(int argc, char **argv) {
  client c = make_client(c);
  c->connect(c);
  return 0;
}

After execution the allocated structure first seem to be allocated correctly and it's instances are accessible until the last printf:

Generic connection (opaque pointer)..
self->name = freebay
self->serv->sun_path = freebay_socket
[1]    210938 segmentation fault (core dumped)
(gdb) p self->serv
$1 = (struct sockaddr_un *) 0x66203d2068746170
(gdb) x self->serv
0x66203d2068746170: Cannot access memory at address 0x66203d2068746170

My question is, why self->serv gets unaccessible after using it inside of the same method?

siery
  • 467
  • 3
  • 20
  • 2
    Can you make a [mcve]? In particular, show the code for the function that calls `connection_connect`. Where did it get the pointer that it passes in? – Nate Eldredge Feb 14 '21 at 17:04

3 Answers3

4

You're not allocating enough memory:

self->serv = malloc(sizeof(struct sockaddr_un *));

You're allocating enough space for a pointer to a struct sockaddr_un, not an instance of one. The pointer size is smaller than the struct size, so when you attempt to write to the struct you write past the end of allocated memory, triggering undefined behavior.

The proper way to allocate the space is:

self->serv = malloc(sizeof(struct sockaddr_un));

Or even better:

self->serv = malloc(sizeof(*self->serv));

As the latter doesn't care what the type is.

Also, this is incorrect:

printf("self->serv->sun_family = %hn\n", self->serv->sun_family);

Because %hn is expecting a short int *, not a short int. Change this to %hd.


With the full example you've given, we can now see that you have two incompatible structs with the same name in each of your .c files.

Your main file defines struct connection as:

typedef struct connection {
  void (*connect)();
} *client;

While connection.c defines it as:

struct connection {
  _Bool (*connect)();
  char *(*get_name)();
  char name[MAX_NAMELEN];
  char hostname[MAX_NAMELEN];
  uint serv_id;
  struct sockaddr_un *serv; // Server socket structure
};

Your main file creates an instance of the former in make_client with this:

client tmp = malloc(sizeof(client));

This is invalid by itself because again you're allocating space for a pointer and not an instance of the struct. It would need to be:

client tmp = malloc(sizeof(*tmp));

But even if you fixed that, you then pass this pointer to connection_connect which expects a pointer to the latter structure, not the former.

All functions that would create an instance of struct connection and modify its members should reside in connection.c where the real definition lives. That's how opaque structs are supposed to work.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • I was thinking the same, I tried both `self->serv = malloc(sizeof(*self->serv))` and `self->serv = malloc(sizeof(struct sockaddr_un))` before and the error is the same. – siery Feb 14 '21 at 17:12
  • @siery This by itself is a problem, but if you're still crashing then there's another error elsewhere in your code. Run it through valgrind, and if you're mismanaging memory it will tell you where. – dbush Feb 14 '21 at 17:14
  • So, you suggest that there should be a method for allocation inside of the opaque structure definition? Something like [this](https://paste.debian.net/1185498/) – siery Feb 14 '21 at 23:32
  • 1
    @siery Something like that, but you're making the same mistake of allocating the size of the pointer instead of what it points to. – dbush Feb 14 '21 at 23:38
  • You are right, taking size of `*tmp` does the job. Thank you. – siery Feb 15 '21 at 11:25
1

Compiling your code I found this:

connection.c:37:43: warning: format specifies type 'short *' but the argument has type 'sa_family_t' (aka 'unsigned char') [-Wformat]
        printf("self->serv->sun_family = %hn\n", self->serv->sun_family);
                                         ~~~     ^~~~~~~~~~~~~~~~~~~~~~
1 warning generated.

If you write:

printf("self->serv->sun_family = %cn\n", self->serv->sun_family);

Program has no error

siery
  • 467
  • 3
  • 20
Ptit Xav
  • 3,006
  • 2
  • 6
  • 15
  • Yes, thank you, this will save some type operations. But that does not change the address access problem. – siery Feb 14 '21 at 22:20
  • Actually after removing the initialization of `self->serv->sun_family`, everything works fine here. – siery Feb 14 '21 at 22:23
-1

You do not allocate self->serv->sun_path before copying data to it. Something must go wrong after that.

Except if sun_path is an array of char.

siery
  • 467
  • 3
  • 20
Ptit Xav
  • 3,006
  • 2
  • 6
  • 15
  • Well, it is an array of type `char[108]`. You can find the definition inside of `sys/un.h`. – siery Feb 14 '21 at 18:03
  • In " server_connection tmp = malloc(sizeof(server_connection)); " you allocate memory for a pointer (server_connection) not a struct (connection) : typedef struct connection {} * server_connection – Ptit Xav Feb 14 '21 at 19:00
  • Yes, for type safety, `server_connection` pointer is now a type, you can see it is assigned to `server.connection` which is of type `server_connection`. I don't want other types deriving from `connection` to be able to be passed there. – siery Feb 14 '21 at 19:09
  • I understand that but you should allocate memory for tmp with sizeof(*server_connection) which will give enough space for a connection struct which is what server_connection pointer points to.. – Ptit Xav Feb 14 '21 at 19:21
  • `connection` type have enough of memory already. I been allocating opaque pointers this way before with no problem. – siery Feb 14 '21 at 19:46
  • Your opaque structure seems to have a size bigger that the size of a pointer : at least 2 pointer to function (opaque definition) and more stuff (name, hostname, ...) in the complete definition. Memory allocated for tmp (= size of a pointer) is really to small. – Ptit Xav Feb 14 '21 at 19:58
  • Maybe you have right, I am not sure about this. However using `client tmp = malloc(sizeof(struct connection));` in the minimal example I have supplied above still does not fix the problem. – siery Feb 14 '21 at 20:08