Coverage Report

Created: 2025-07-11 06:08

/src/yara/libyara/proc/linux.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
Copyright (c) 2007-2021. The YARA Authors. All Rights Reserved.
3
4
Redistribution and use in source and binary forms, with or without modification,
5
are permitted provided that the following conditions are met:
6
7
1. Redistributions of source code must retain the above copyright notice, this
8
list of conditions and the following disclaimer.
9
10
2. Redistributions in binary form must reproduce the above copyright notice,
11
this list of conditions and the following disclaimer in the documentation and/or
12
other materials provided with the distribution.
13
14
3. Neither the name of the copyright holder nor the names of its contributors
15
may be used to endorse or promote products derived from this software without
16
specific prior written permission.
17
18
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
*/
29
30
#if defined(USE_LINUX_PROC)
31
32
#define _FILE_OFFSET_BITS 64
33
34
#include <assert.h>
35
#include <errno.h>
36
#include <fcntl.h>
37
#include <inttypes.h>
38
#include <sys/mman.h>
39
#include <sys/stat.h>
40
#include <sys/sysmacros.h>
41
#include <sys/types.h>
42
#include <sys/wait.h>
43
#include <unistd.h>
44
#include <yara/error.h>
45
#include <yara/globals.h>
46
#include <yara/libyara.h>
47
#include <yara/mem.h>
48
#include <yara/proc.h>
49
#include <yara/strutils.h>
50
51
typedef struct _YR_PROC_INFO
52
{
53
  int pid;
54
  int mem_fd;
55
  int pagemap_fd;
56
  FILE* maps;
57
  uint64_t map_offset;
58
  uint64_t next_block_end;
59
  int page_size;
60
  char map_path[YR_MAX_PATH];
61
  uint64_t map_dmaj;
62
  uint64_t map_dmin;
63
  uint64_t map_ino;
64
} YR_PROC_INFO;
65
66
static int page_size = -1;
67
68
int _yr_process_attach(int pid, YR_PROC_ITERATOR_CTX* context)
69
0
{
70
0
  char buffer[256];
71
72
0
  page_size = sysconf(_SC_PAGE_SIZE);
73
0
  if (page_size < 0)
74
0
    page_size = 4096;
75
76
0
  YR_PROC_INFO* proc_info = (YR_PROC_INFO*) yr_malloc(sizeof(YR_PROC_INFO));
77
78
0
  if (proc_info == NULL)
79
0
    return ERROR_INSUFFICIENT_MEMORY;
80
81
0
  proc_info->pid = pid;
82
0
  proc_info->maps = NULL;
83
0
  proc_info->mem_fd = -1;
84
0
  proc_info->pagemap_fd = -1;
85
0
  proc_info->next_block_end = 0;
86
87
0
  snprintf(buffer, sizeof(buffer), "/proc/%u/maps", pid);
88
0
  proc_info->maps = fopen(buffer, "r");
89
90
0
  if (proc_info->maps == NULL)
91
0
    goto err;
92
93
0
  snprintf(buffer, sizeof(buffer), "/proc/%u/mem", pid);
94
0
  proc_info->mem_fd = open(buffer, O_RDONLY);
95
96
0
  if (proc_info->mem_fd == -1)
97
0
    goto err;
98
99
0
  snprintf(buffer, sizeof(buffer), "/proc/%u/pagemap", pid);
100
0
  proc_info->pagemap_fd = open(buffer, O_RDONLY);
101
102
0
  if (proc_info->pagemap_fd == -1)
103
0
    goto err;
104
105
0
  context->proc_info = proc_info;
106
107
0
  return ERROR_SUCCESS;
108
109
0
err:
110
0
  if (proc_info)
111
0
  {
112
0
    if (proc_info->pagemap_fd != -1)
113
0
      close(proc_info->pagemap_fd);
114
115
0
    if (proc_info->mem_fd != -1)
116
0
      close(proc_info->mem_fd);
117
118
0
    if (proc_info->maps != NULL)
119
0
      fclose(proc_info->maps);
120
121
0
    yr_free(proc_info);
122
0
  }
123
124
0
  return ERROR_COULD_NOT_ATTACH_TO_PROCESS;
125
0
}
126
127
int _yr_process_detach(YR_PROC_ITERATOR_CTX* context)
128
0
{
129
0
  YR_PROC_INFO* proc_info = (YR_PROC_INFO*) context->proc_info;
130
0
  if (proc_info)
131
0
  {
132
0
    fclose(proc_info->maps);
133
0
    close(proc_info->mem_fd);
134
0
    close(proc_info->pagemap_fd);
135
0
  }
136
137
0
  if (context->buffer != NULL)
138
0
  {
139
0
    munmap((void*) context->buffer, context->buffer_size);
140
0
    context->buffer = NULL;
141
0
    context->buffer_size = 0;
142
0
  }
143
144
0
  return ERROR_SUCCESS;
145
0
}
146
147
YR_API const uint8_t* yr_process_fetch_memory_block_data(YR_MEMORY_BLOCK* block)
148
0
{
149
0
  const uint8_t* result = NULL;
150
0
  uint64_t* pagemap = NULL;
151
152
0
  YR_PROC_ITERATOR_CTX* context = (YR_PROC_ITERATOR_CTX*) block->context;
153
0
  YR_PROC_INFO* proc_info = (YR_PROC_INFO*) context->proc_info;
154
155
0
  if (context->buffer != NULL)
156
0
  {
157
0
    munmap((void*) context->buffer, context->buffer_size);
158
0
    context->buffer = NULL;
159
0
    context->buffer_size = 0;
160
0
  }
161
162
0
  int fd = -2;  // Assume mapping not connected with a file.
163
164
  // Only try mapping the file if it has a path and belongs to a device
165
0
  if (strlen(proc_info->map_path) > 0 &&
166
0
      !(proc_info->map_dmaj == 0 && proc_info->map_dmin == 0))
167
0
  {
168
0
    struct stat st;
169
170
0
    if (stat(proc_info->map_path, &st) < 0)
171
0
    {
172
      // Why should stat fail after file open? Treat like missing.
173
0
      fd = -1;
174
0
    }
175
0
    else if (
176
0
        (major(st.st_dev) != proc_info->map_dmaj) ||
177
0
        (minor(st.st_dev) != proc_info->map_dmin) ||
178
0
        (st.st_ino != proc_info->map_ino))
179
0
    {
180
      // Wrong file, may have been replaced. Treat like missing.
181
0
      fd = -1;
182
0
    }
183
0
    else if (st.st_size < proc_info->map_offset + block->size)
184
0
    {
185
      // Mapping extends past end of file. Treat like missing.
186
0
      fd = -1;
187
0
    }
188
0
    else if ((st.st_mode & S_IFMT) != S_IFREG)
189
0
    {
190
      // Correct filesystem object, but not a regular file. Treat like
191
      // uninitialized mapping.
192
0
      fd = -2;
193
0
    }
194
0
    else
195
0
    {
196
0
      fd = open(proc_info->map_path, O_RDONLY);
197
      // Double-check against race conditions
198
0
      struct stat st2;
199
0
      if (fstat(fd, &st2) < 0)
200
0
      {
201
0
        close(fd);
202
0
        fd = -1;
203
0
      }
204
0
      else if ((st.st_dev != st2.st_dev) || (st.st_ino != st2.st_ino))
205
0
      {
206
        // File has been changed from under us, so ignore.
207
0
        close(fd);
208
0
        fd = -1;
209
0
      }
210
0
    }
211
0
  }
212
213
0
  if (fd >= 0)
214
0
  {
215
0
    context->buffer = mmap(
216
0
        NULL,
217
0
        block->size,
218
0
        PROT_READ | PROT_WRITE,
219
0
        MAP_PRIVATE,
220
0
        fd,
221
0
        proc_info->map_offset);
222
0
    close(fd);
223
0
    if (context->buffer == MAP_FAILED)
224
0
    {
225
      // Notify the code below that we couldn't read from the file
226
      // fallback to pread() from the process
227
0
      fd = -1;
228
0
    }
229
0
    context->buffer_size = block->size;
230
0
  }
231
232
0
  if (fd < 0)
233
0
  {
234
0
    context->buffer = mmap(
235
0
        NULL,
236
0
        block->size,
237
0
        PROT_READ | PROT_WRITE,
238
0
        MAP_PRIVATE | MAP_ANONYMOUS,
239
0
        -1,
240
0
        0);
241
0
    if (context->buffer == MAP_FAILED)
242
0
    {
243
0
      context->buffer = NULL;
244
0
      context->buffer_size = 0;
245
0
      goto _exit;
246
0
    }
247
0
    context->buffer_size = block->size;
248
0
  }
249
250
  // If mapping can't be accessed through the filesystem, read everything from
251
  // target process VM.
252
0
  if (fd == -1)
253
0
  {
254
0
    if (pread(
255
0
            proc_info->mem_fd,
256
0
            (void*) context->buffer,
257
0
            block->size,
258
0
            block->base) == -1)
259
0
    {
260
0
      goto _exit;
261
0
    }
262
0
  }
263
0
  else
264
0
  {
265
0
    pagemap = calloc(block->size / page_size, sizeof(uint64_t));
266
0
    if (pagemap == NULL)
267
0
    {
268
0
      goto _exit;
269
0
    }
270
0
    if (pread(
271
0
            proc_info->pagemap_fd,
272
0
            pagemap,
273
0
            sizeof(uint64_t) * block->size / page_size,
274
0
            sizeof(uint64_t) * block->base / page_size) == -1)
275
0
    {
276
0
      goto _exit;
277
0
    }
278
279
0
    for (uint64_t i = 0; i < block->size / page_size; i++)
280
0
    {
281
0
      if (pagemap[i] >> 61 == 0)
282
0
      {
283
0
        continue;
284
0
      }
285
      // Overwrite our mapping if the page is present, file-backed, or
286
      // swap-backed and if it differs from our mapping.
287
0
      uint8_t buffer[page_size];
288
289
0
      if (pread(
290
0
              proc_info->mem_fd,
291
0
              buffer,
292
0
              page_size,
293
0
              block->base + i * page_size) == -1)
294
0
      {
295
0
        goto _exit;
296
0
      }
297
298
0
      if (memcmp(
299
0
              (void*) context->buffer + i * page_size,
300
0
              (void*) buffer,
301
0
              page_size) != 0)
302
0
      {
303
0
        memcpy(
304
0
            (void*) context->buffer + i * page_size, (void*) buffer, page_size);
305
0
      }
306
0
    }
307
0
  }
308
309
0
  result = context->buffer;
310
311
0
_exit:;
312
313
0
  if (pagemap)
314
0
  {
315
0
    free(pagemap);
316
0
    pagemap = NULL;
317
0
  }
318
319
0
  YR_DEBUG_FPRINTF(2, stderr, "- %s() {} = %p\n", __FUNCTION__, result);
320
321
0
  return result;
322
0
}
323
324
YR_API YR_MEMORY_BLOCK* yr_process_get_next_memory_block(
325
    YR_MEMORY_BLOCK_ITERATOR* iterator)
326
0
{
327
0
  YR_PROC_ITERATOR_CTX* context = (YR_PROC_ITERATOR_CTX*) iterator->context;
328
0
  YR_PROC_INFO* proc_info = (YR_PROC_INFO*) context->proc_info;
329
330
0
  char buffer[YR_MAX_PATH];
331
0
  char perm[5];
332
333
0
  uint64_t begin, end;
334
0
  uint64_t current_begin = context->current_block.base +
335
0
                           context->current_block.size;
336
337
0
  uint64_t max_process_memory_chunk;
338
339
0
  yr_get_configuration_uint64(
340
0
      YR_CONFIG_MAX_PROCESS_MEMORY_CHUNK, &max_process_memory_chunk);
341
342
0
  iterator->last_error = ERROR_SUCCESS;
343
344
0
  if (proc_info->next_block_end <= current_begin)
345
0
  {
346
0
    int path_start, n = 0;
347
0
    char* p;
348
349
0
    while (fgets(buffer, sizeof(buffer), proc_info->maps) != NULL)
350
0
    {
351
      // locate the '\n' character
352
0
      p = strrchr(buffer, '\n');
353
      // If we haven't read the whole line, skip over the rest.
354
0
      if (p == NULL)
355
0
      {
356
0
        int c;
357
0
        do
358
0
        {
359
0
          c = fgetc(proc_info->maps);
360
0
        } while (c >= 0 && c != '\n');
361
0
      }
362
      // otherwise remove '\n' at the end of the line
363
0
      else
364
0
      {
365
0
        *p = '\0';
366
0
      }
367
368
      // Each row in /proc/$PID/maps describes a region of contiguous virtual
369
      // memory in a process or thread. Each row has the following fields:
370
      //
371
      // address           perms offset  dev   inode   pathname
372
      // 08048000-08056000 r-xp 00000000 03:0c 64593   /usr/sbin/gpm
373
      //
374
0
      n = sscanf(
375
0
          buffer,
376
0
          "%" SCNx64 "-%" SCNx64 " %4s "
377
0
          "%" SCNx64 " %" SCNx64 ":%" SCNx64 " %" SCNu64 " %n",
378
0
          &begin,
379
0
          &end,
380
0
          perm,
381
0
          &(proc_info->map_offset),
382
0
          &(proc_info->map_dmaj),
383
0
          &(proc_info->map_dmin),
384
0
          &(proc_info->map_ino),
385
0
          &path_start);
386
387
      // If the row was parsed correctly sscan must return 7.
388
0
      if (n == 7)
389
0
      {
390
        // skip the memory region that doesn't have read permission.
391
0
        if (perm[0] != 'r')
392
0
        {
393
0
          continue;
394
0
        }
395
        // path_start contains the offset within buffer where the path starts,
396
        // the path should start with /.
397
0
        if (buffer[path_start] == '/')
398
0
          strncpy(
399
0
              proc_info->map_path,
400
0
              buffer + path_start,
401
0
              sizeof(proc_info->map_path) - 1);
402
0
        else
403
0
          proc_info->map_path[0] = '\0';
404
0
        break;
405
0
      }
406
0
    }
407
408
0
    if (n == 7)
409
0
    {
410
0
      current_begin = begin;
411
0
      proc_info->next_block_end = end;
412
0
    }
413
0
    else
414
0
    {
415
0
      YR_DEBUG_FPRINTF(2, stderr, "- %s() = NULL\n", __FUNCTION__);
416
0
      return NULL;
417
0
    }
418
0
  }
419
420
0
  context->current_block.base = current_begin;
421
0
  context->current_block.size = yr_min(
422
0
      proc_info->next_block_end - current_begin, max_process_memory_chunk);
423
424
0
  assert(context->current_block.size > 0);
425
426
0
  YR_DEBUG_FPRINTF(
427
0
      2,
428
0
      stderr,
429
0
      "- %s() {} = %p // .base=0x%" PRIx64 " .size=%" PRIu64 "\n",
430
0
      __FUNCTION__,
431
0
      context->current_block,
432
0
      context->current_block.base,
433
0
      context->current_block.size);
434
435
0
  return &context->current_block;
436
0
}
437
438
YR_API YR_MEMORY_BLOCK* yr_process_get_first_memory_block(
439
    YR_MEMORY_BLOCK_ITERATOR* iterator)
440
0
{
441
0
  YR_DEBUG_FPRINTF(2, stderr, "+ %s() {\n", __FUNCTION__);
442
443
0
  YR_MEMORY_BLOCK* result = NULL;
444
0
  YR_PROC_ITERATOR_CTX* context = (YR_PROC_ITERATOR_CTX*) iterator->context;
445
0
  YR_PROC_INFO* proc_info = (YR_PROC_INFO*) context->proc_info;
446
447
0
  if (fseek(proc_info->maps, 0, SEEK_SET) != 0)
448
0
  {
449
0
    result = NULL;
450
0
    goto _exit;
451
0
  }
452
453
0
  proc_info->next_block_end = 0;
454
455
0
  result = yr_process_get_next_memory_block(iterator);
456
457
0
_exit:
458
459
0
  if (result == NULL)
460
0
    iterator->last_error = ERROR_COULD_NOT_READ_PROCESS_MEMORY;
461
462
0
  YR_DEBUG_FPRINTF(2, stderr, "} = %p // %s()\n", result, __FUNCTION__);
463
464
0
  return result;
465
0
}
466
467
#endif