• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/firmware-utils/src/npk_pack_kernel.c

  1 // SPDX-License-Identifier: GPL-3.0-only
  2 /*
  3  * NPK Kernel Packer - C implementation
  4  *
  5  * This tool creates MikroTik NPK packages containing kernel images.
  6  * It's a C reimplementation of the Python poc_pack_kernel.py tool
  7  * written by John Thomson <git@johnthomson.fastmail.com.au>
  8  * which is based on npkpy https://github.com/botlabsDev/npkpy
  9  * provided by @botlabsDev under GPL-3.0.
 10  *
 11  * created within minutes using Claude Sonnet 4 instructed by
 12  * Daniel Golle <daniel@makrotopia.org>
 13  */
 14 
 15 #include <stdio.h>
 16 #include <stdlib.h>
 17 #include <string.h>
 18 #include <stdint.h>
 19 #include <sys/stat.h>
 20 #include <unistd.h>
 21 #include <zlib.h>
 22 #include <arpa/inet.h>
 23 #include <err.h>
 24 #include <fcntl.h>
 25 
 26 /* Ensure we have the file type constants */
 27 #ifndef S_IFDIR
 28 #define S_IFDIR 0040000 /* Directory */
 29 #endif
 30 #ifndef S_IFREG
 31 #define S_IFREG 0100000 /* Regular file */
 32 #endif
 33 
 34 /* NPK format constants */
 35 #define NPK_MAGIC_BYTES 0xBAD0F11E
 36 #define NPK_NULL_BLOCK 22
 37 #define NPK_SQUASH_FS_IMAGE 21
 38 #define NPK_ZLIB_COMPRESSED_DATA 4
 39 
 40 /* Container alignment */
 41 #define SQUASHFS_ALIGNMENT 0x1000
 42 
 43 /* File mode constants (from stat.h) */
 44 #define FILE_MODE_EXEC (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
 45 #define FILE_MODE_REG (FILE_MODE_EXEC & ~(S_IXUSR | S_IXGRP | S_IXOTH))
 46 
 47 static char *progname;
 48 
 49 #pragma pack(push, 1)
 50 
 51 /* NPK file header */
 52 typedef struct {
 53         uint32_t magic;         /* Magic bytes: 0x1EF1D0BA */
 54         uint32_t payload_len;   /* Length of all containers */
 55 } npk_header_t;
 56 
 57 /* Container header */
 58 typedef struct {
 59         uint16_t cnt_id;        /* Container type ID */
 60         uint32_t payload_len;   /* Container payload length */
 61 } container_header_t;
 62 
 63 /* Zlib compressed object header */
 64 typedef struct {
 65         uint16_t obj_mode;      /* File mode (stat.h format) */
 66         uint16_t zeroes1[3];    /* Padding */
 67         uint32_t timestamps[3]; /* create, access, modify timestamps */
 68         uint32_t zeroes2;       /* More padding */
 69         uint32_t payload_len;   /* Payload length */
 70         uint16_t name_len;      /* Name length */
 71         /* Followed by name and payload */
 72 } zlib_obj_header_t;
 73 
 74 #pragma pack(pop)
 75 
 76 /* Structure to hold container data */
 77 typedef struct {
 78         uint16_t cnt_id;
 79         uint32_t payload_len;
 80         uint8_t *payload;
 81 } container_t;
 82 
 83 /* Structure to hold zlib object data */
 84 typedef struct {
 85         uint16_t obj_mode;
 86         uint32_t timestamps[3];
 87         char *name;
 88         uint8_t *payload;
 89         uint32_t payload_len;
 90 } zlib_object_t;
 91 
 92 /*
 93  * Calculate the size needed for a container including header
 94  */
 95 static size_t container_full_size(const container_t *cnt)
 96 {
 97         return sizeof(container_header_t) + cnt->payload_len;
 98 }
 99 
100 /*
101  * Write a container to a buffer
102  */
103 static size_t write_container(uint8_t *buffer, const container_t *cnt)
104 {
105         container_header_t *header = (container_header_t *)buffer;
106         header->cnt_id = cnt->cnt_id;
107         header->payload_len = cnt->payload_len;
108 
109         if (cnt->payload && cnt->payload_len > 0) {
110                 memcpy(buffer + sizeof(container_header_t), cnt->payload, cnt->payload_len);
111         }
112 
113         return container_full_size(cnt);
114 }
115 
116 /*
117  * Create a null block container for alignment
118  */
119 static container_t create_null_block(size_t alignment_size)
120 {
121         container_t cnt = { 0 };
122         size_t header_size, padding;
123 
124         cnt.cnt_id = NPK_NULL_BLOCK;
125 
126         /* Calculate padding needed to align next container to boundary */
127         header_size = sizeof(npk_header_t) + sizeof(container_header_t);
128         padding = alignment_size - (header_size + sizeof(container_header_t)) % alignment_size;
129         if (padding == alignment_size)
130                 padding = 0;
131 
132         cnt.payload_len = padding;
133         if (padding > 0) {
134                 if ((cnt.payload = calloc(1, padding)) == NULL)
135                         err(EXIT_FAILURE, "calloc");
136         }
137 
138         return cnt;
139 }
140 
141 /*
142  * Create a SquashFS container with dummy payload
143  */
144 static container_t create_squashfs_container(void)
145 {
146         container_t cnt = { 0 };
147         cnt.cnt_id = NPK_SQUASH_FS_IMAGE;
148         cnt.payload_len = SQUASHFS_ALIGNMENT;
149 
150         if ((cnt.payload = calloc(1, cnt.payload_len)) == NULL)
151                 err(EXIT_FAILURE, "calloc");
152 
153         return cnt;
154 }
155 
156 /*
157  * Serialize a zlib object to binary format
158  */
159 static uint8_t *serialize_zlib_object(const zlib_object_t *obj, size_t *out_size)
160 {
161         size_t name_len = strlen(obj->name);
162         size_t total_size = sizeof(zlib_obj_header_t) + name_len + obj->payload_len;
163         uint8_t *buffer;
164         zlib_obj_header_t *header;
165 
166         if ((buffer = malloc(total_size)) == NULL)
167                 err(EXIT_FAILURE, "malloc");
168 
169         header = (zlib_obj_header_t *)buffer;
170         header->obj_mode = obj->obj_mode;
171         memset(header->zeroes1, 0, sizeof(header->zeroes1));
172         memcpy(header->timestamps, obj->timestamps, sizeof(header->timestamps));
173         header->zeroes2 = 0;
174         header->payload_len = obj->payload_len;
175         header->name_len = name_len;
176 
177         /* Copy name */
178         memcpy(buffer + sizeof(zlib_obj_header_t), obj->name, name_len);
179 
180         /* Copy payload */
181         if (obj->payload && obj->payload_len > 0) {
182                 memcpy(buffer + sizeof(zlib_obj_header_t) + name_len, obj->payload,
183                        obj->payload_len);
184         }
185 
186         *out_size = total_size;
187         return buffer;
188 }
189 
190 /*
191  * Compress data using the exact same method as Python implementation
192  * This matches the Python set_cnt_payload_decompressed function exactly
193  */
194 static uint8_t *compress_zlib_data(const uint8_t *input, size_t input_len, size_t *out_len,
195                                    size_t block_size)
196 {
197         size_t max_output_size = input_len * 2 + 1024; /* Conservative estimate */
198         uint8_t *buffer_out;
199         size_t output_offset = 0;
200         size_t offset = 0;
201         uint32_t adler32;
202 
203         if ((buffer_out = malloc(max_output_size)) == NULL)
204                 err(EXIT_FAILURE, "malloc");
205 
206         /* Compression method magic - matches Python b"\x78\x01" */
207         buffer_out[output_offset++] = 0x78;
208         buffer_out[output_offset++] = 0x01;
209 
210         /* Initialize adler32 - matches Python zlib.adler32(b"") */
211         adler32 = adler32_z(1L, NULL, 0);
212 
213         /* Process data in blocks - matches Python while loop */
214         while (offset < input_len) {
215                 size_t buffer_in_len = (offset + block_size <= input_len) ? block_size :
216                                                                             (input_len - offset);
217                 const uint8_t *buffer_in = input + offset;
218                 uLong max_block_compressed = compressBound(buffer_in_len);
219                 uint8_t *compressed;
220                 uLong compressed_len;
221                 int result;
222 
223                 if ((compressed = malloc(max_block_compressed)) == NULL)
224                         err(EXIT_FAILURE, "malloc");
225 
226                 compressed_len = max_block_compressed;
227                 result = compress2(compressed, &compressed_len, buffer_in, buffer_in_len,
228                                    0); /* level=0 */
229 
230                 if (result != Z_OK)
231                         err(EXIT_FAILURE, "compress2 failed: %d", result);
232 
233                 /* Extract the right portion based on block type */
234                 if (buffer_in_len == block_size) {
235                         /* Not-last-block: block = b"\x00" + compressed[3:-4] */
236                         /* Skip first 3 bytes and last 4 bytes */
237                         size_t copy_len = compressed_len - 7;
238 
239                         buffer_out[output_offset++] = 0x00;
240 
241                         if (output_offset + copy_len >= max_output_size) {
242                                 max_output_size *= 2;
243                                 if ((buffer_out = realloc(buffer_out, max_output_size)) == NULL)
244                                         err(EXIT_FAILURE, "realloc");
245                         }
246                         memcpy(buffer_out + output_offset, compressed + 3, copy_len);
247                         output_offset += copy_len;
248                 } else {
249                         /* Last block: block = compressed[2:-4] */
250                         size_t copy_len =
251                                 compressed_len - 6; /* Skip first 2 bytes and last 4 bytes */
252 
253                         if (output_offset + copy_len >= max_output_size) {
254                                 max_output_size *= 2;
255                                 if ((buffer_out = realloc(buffer_out, max_output_size)) == NULL)
256                                         err(EXIT_FAILURE, "realloc");
257                         }
258                         memcpy(buffer_out + output_offset, compressed + 2, copy_len);
259                         output_offset += copy_len;
260                 }
261 
262                 /* Update adler32 - matches Python zlib.adler32(compressed[7:-4], adler32) */
263                 if (compressed_len > 11) { /* Ensure we have enough bytes */
264                         adler32 = adler32_z(adler32, compressed + 7, compressed_len - 11);
265                 }
266 
267                 free(compressed);
268                 offset += block_size;
269         }
270 
271         /* Add final adler32 checksum - matches Python struct.pack(">L", adler32) */
272         if (output_offset + 4 >= max_output_size) {
273                 max_output_size += 4;
274                 if ((buffer_out = realloc(buffer_out, max_output_size)) == NULL)
275                         err(EXIT_FAILURE, "realloc");
276         }
277 
278         buffer_out[output_offset++] = (adler32 >> 24) & 0xFF;
279         buffer_out[output_offset++] = (adler32 >> 16) & 0xFF;
280         buffer_out[output_offset++] = (adler32 >> 8) & 0xFF;
281         buffer_out[output_offset++] = adler32 & 0xFF;
282 
283         /* Shrink to actual size */
284         if ((buffer_out = realloc(buffer_out, output_offset)) == NULL)
285                 err(EXIT_FAILURE, "realloc final");
286 
287         *out_len = output_offset;
288         return buffer_out;
289 }
290 
291 /*
292  * Create a zlib compressed container with kernel objects
293  */
294 static container_t create_zlib_container(const uint8_t *kernel_data, size_t kernel_size)
295 {
296         container_t cnt = { 0 };
297         zlib_object_t objects[3];
298         uint8_t *obj_data[3];
299         size_t obj_sizes[3];
300         size_t total_uncompressed = 0;
301         uint8_t *uncompressed_data;
302         size_t offset = 0;
303         size_t compressed_len;
304         uint8_t *compressed_data;
305         int i;
306 
307         cnt.cnt_id = NPK_ZLIB_COMPRESSED_DATA;
308 
309         /* Create zlib objects */
310 
311         /* Boot directory object */
312         objects[0].obj_mode = S_IFDIR | FILE_MODE_EXEC;
313         objects[0].name = "boot";
314         objects[0].payload = NULL;
315         objects[0].payload_len = 0;
316         memset(objects[0].timestamps, 0, sizeof(objects[0].timestamps));
317 
318         /* Kernel file object */
319         objects[1].obj_mode = S_IFREG | FILE_MODE_EXEC;
320         objects[1].name = "boot/kernel";
321         objects[1].payload = (uint8_t *)kernel_data;
322         objects[1].payload_len = kernel_size;
323         memset(objects[1].timestamps, 0, sizeof(objects[1].timestamps));
324 
325         /* UPGRADED file object */
326         objects[2].obj_mode = S_IFREG | FILE_MODE_REG;
327         objects[2].name = "UPGRADED";
328         if ((objects[2].payload = calloc(1, 0x20)) == NULL)
329                 err(EXIT_FAILURE, "calloc");
330         objects[2].payload_len = 0x20;
331         memset(objects[2].timestamps, 0, sizeof(objects[2].timestamps));
332 
333         /* Serialize objects */
334         for (i = 0; i < 3; i++) {
335                 obj_data[i] = serialize_zlib_object(&objects[i], &obj_sizes[i]);
336                 total_uncompressed += obj_sizes[i];
337         }
338 
339         /* Concatenate all objects */
340         if ((uncompressed_data = malloc(total_uncompressed)) == NULL)
341                 err(EXIT_FAILURE, "malloc");
342 
343         for (i = 0; i < 3; i++) {
344                 memcpy(uncompressed_data + offset, obj_data[i], obj_sizes[i]);
345                 offset += obj_sizes[i];
346                 free(obj_data[i]);
347         }
348 
349         /* Compress the data */
350         compressed_data =
351                 compress_zlib_data(uncompressed_data, total_uncompressed, &compressed_len, 0x8000);
352 
353         free(uncompressed_data);
354         free(objects[2].payload); /* Free the UPGRADED payload we allocated */
355 
356         cnt.payload = compressed_data;
357         cnt.payload_len = compressed_len;
358 
359         return cnt;
360 }
361 
362 /*
363  * Read file contents into memory
364  */
365 static uint8_t *read_file(const char *filename, size_t *file_size)
366 {
367         int fd;
368         struct stat st;
369         uint8_t *buffer;
370         ssize_t read_bytes;
371 
372         if ((fd = open(filename, O_RDONLY)) == -1)
373                 err(EXIT_FAILURE, "%s", filename);
374 
375         if (fstat(fd, &st) == -1)
376                 err(EXIT_FAILURE, "%s", filename);
377 
378         *file_size = st.st_size;
379 
380         if ((buffer = malloc(*file_size)) == NULL)
381                 err(EXIT_FAILURE, "malloc");
382 
383         if ((read_bytes = read(fd, buffer, *file_size)) != *file_size)
384                 err(EXIT_FAILURE, "read %s", filename);
385 
386         close(fd);
387         return buffer;
388 }
389 
390 /*
391  * Write NPK file
392  */
393 static int write_npk_file(const char *filename, container_t *containers, int num_containers)
394 {
395         int fd;
396         uint32_t total_payload = 0;
397         npk_header_t header;
398         int i;
399 
400         /* Calculate total payload size */
401         for (i = 0; i < num_containers; i++) {
402                 total_payload += container_full_size(&containers[i]);
403         }
404 
405         if ((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1)
406                 err(EXIT_FAILURE, "%s", filename);
407 
408         /* Write NPK header */
409         header.magic = NPK_MAGIC_BYTES;
410         header.payload_len = total_payload;
411 
412         if (write(fd, &header, sizeof(header)) != sizeof(header))
413                 err(EXIT_FAILURE, "write header");
414 
415         /* Write containers */
416         for (i = 0; i < num_containers; i++) {
417                 size_t container_size = container_full_size(&containers[i]);
418                 uint8_t *buffer;
419 
420                 if ((buffer = malloc(container_size)) == NULL)
421                         err(EXIT_FAILURE, "malloc");
422 
423                 write_container(buffer, &containers[i]);
424 
425                 if (write(fd, buffer, container_size) != container_size)
426                         err(EXIT_FAILURE, "write container");
427 
428                 free(buffer);
429         }
430 
431         close(fd);
432         return 0;
433 }
434 
435 /*
436  * Print usage information
437  */
438 static void usage(void)
439 {
440         fprintf(stderr, "Usage: %s <kernel> <output>\n", progname);
441         exit(EXIT_FAILURE);
442 }
443 
444 int main(int argc, char *argv[])
445 {
446         const char *kernel_file;
447         const char *output_file;
448         size_t kernel_size;
449         uint8_t *kernel_data;
450         container_t containers[3];
451         int i;
452 
453         progname = argv[0];
454 
455         if (argc != 3)
456                 usage();
457 
458         kernel_file = argv[1];
459         output_file = argv[2];
460 
461         /* Read kernel file */
462         kernel_data = read_file(kernel_file, &kernel_size);
463 
464         /* Create containers */
465         /* Null block for alignment */
466         containers[0] = create_null_block(SQUASHFS_ALIGNMENT);
467 
468         /* SquashFS container */
469         containers[1] = create_squashfs_container();
470 
471         /* Zlib container with kernel */
472         containers[2] = create_zlib_container(kernel_data, kernel_size);
473 
474         /* Write NPK file */
475         write_npk_file(output_file, containers, 3);
476 
477         /* Cleanup */
478         free(kernel_data);
479         for (i = 0; i < 3; i++)
480                 if (containers[i].payload)
481                         free(containers[i].payload);
482 
483         return EXIT_SUCCESS;
484 }
485 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt