Coverage Report

Created: 2023-03-26 07:38

/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
#include <assert.h>
33
#include <errno.h>
34
#include <fcntl.h>
35
#include <inttypes.h>
36
#include <sys/mman.h>
37
#include <sys/stat.h>
38
#include <sys/sysmacros.h>
39
#include <sys/types.h>
40
#include <sys/wait.h>
41
#include <unistd.h>
42
#include <yara/error.h>
43
#include <yara/globals.h>
44
#include <yara/libyara.h>
45
#include <yara/mem.h>
46
#include <yara/proc.h>
47
#include <yara/strutils.h>
48
49
typedef struct _YR_PROC_INFO
50
{
51
  int pid;
52
  int mem_fd;
53
  int pagemap_fd;
54
  FILE* maps;
55
  uint64_t map_offset;
56
  uint64_t next_block_end;
57
  int page_size;
58
  char map_path[PATH_MAX];
59
  uint64_t map_dmaj;
60
  uint64_t map_dmin;
61
  uint64_t map_ino;
62
} YR_PROC_INFO;
63
64
static int page_size = -1;
65
66
int _yr_process_attach(int pid, YR_PROC_ITERATOR_CTX* context)
67
0
{
68
0
  char buffer[256];
69
70
0
  page_size = sysconf(_SC_PAGE_SIZE);
71
0
  if (page_size < 0)
72
0
    page_size = 4096;
73
74
0
  YR_PROC_INFO* proc_info = (YR_PROC_INFO*) yr_malloc(sizeof(YR_PROC_INFO));
75
76
0
  if (proc_info == NULL)
77
0
    return ERROR_INSUFFICIENT_MEMORY;
78
79
0
  proc_info->pid = pid;
80
0
  proc_info->maps = NULL;
81
0
  proc_info->mem_fd = -1;
82
0
  proc_info->pagemap_fd = -1;
83
0
  proc_info->next_block_end = 0;
84
85
0
  snprintf(buffer, sizeof(buffer), "/proc/%u/maps", pid);
86
0
  proc_info->maps = fopen(buffer, "r");
87
88
0
  if (proc_info->maps == NULL)
89
0
    goto err;
90
91
0
  snprintf(buffer, sizeof(buffer), "/proc/%u/mem", pid);
92
0
  proc_info->mem_fd = open(buffer, O_RDONLY);
93
94
0
  if (proc_info->mem_fd == -1)
95
0
    goto err;
96
97
0
  snprintf(buffer, sizeof(buffer), "/proc/%u/pagemap", pid);
98
0
  proc_info->pagemap_fd = open(buffer, O_RDONLY);
99
100
0
  if (proc_info->mem_fd == -1)
101
0
    goto err;
102
103
0
  context->proc_info = proc_info;
104
105
0
  return ERROR_SUCCESS;
106
107
0
err:
108
0
  if (proc_info)
109
0
  {
110
0
    if (proc_info->pagemap_fd != -1)
111
0
      close(proc_info->pagemap_fd);
112
113
0
    if (proc_info->mem_fd != -1)
114
0
      close(proc_info->mem_fd);
115
116
0
    if (proc_info->maps != NULL)
117
0
      fclose(proc_info->maps);
118
119
0
    yr_free(proc_info);
120
0
  }
121
122
0
  return ERROR_COULD_NOT_ATTACH_TO_PROCESS;
123
0
}
124
125
int _yr_process_detach(YR_PROC_ITERATOR_CTX* context)
126
0
{
127
0
  YR_PROC_INFO* proc_info = (YR_PROC_INFO*) context->proc_info;
128
0
  if (proc_info)
129
0
  {
130
0
    fclose(proc_info->maps);
131
0
    close(proc_info->mem_fd);
132
0
    close(proc_info->pagemap_fd);
133
0
  }
134
135
0
  if (context->buffer != NULL)
136
0
  {
137
0
    munmap((void*) context->buffer, context->buffer_size);
138
0
    context->buffer = NULL;
139
0
    context->buffer_size = 0;
140
0
  }
141
142
0
  return ERROR_SUCCESS;
143
0
}
144
145
YR_API const uint8_t* yr_process_fetch_memory_block_data(YR_MEMORY_BLOCK* block)
146
0
{
147
0
  const uint8_t* result = NULL;
148
0
  uint64_t* pagemap = NULL;
149
150
0
  YR_PROC_ITERATOR_CTX* context = (YR_PROC_ITERATOR_CTX*) block->context;
151
0
  YR_PROC_INFO* proc_info = (YR_PROC_INFO*) context->proc_info;
152
153
0
  if (context->buffer != NULL)
154
0
  {
155
0
    munmap((void*) context->buffer, context->buffer_size);
156
0
    context->buffer = NULL;
157
0
    context->buffer_size = 0;
158
0
  }
159
160
0
  int fd = -2;  // Assume mapping not connected with a file.
161
162
  // Only try mapping the file if it has a path and belongs to a device
163
0
  if (strlen(proc_info->map_path) > 0 &&
164
0
      !(proc_info->map_dmaj == 0 && proc_info->map_dmin == 0))
165
0
  {
166
0
    struct stat st;
167
0
    fd = open(proc_info->map_path, O_RDONLY);
168
169
0
    if (fd < 0)
170
0
    {
171
0
      fd = -1;  // File does not exist.
172
0
    }
173
0
    else if (fstat(fd, &st) < 0)
174
0
    {
175
      // Why should stat fail after file open? Treat like missing.
176
0
      close(fd);
177
0
      fd = -1;
178
0
    }
179
0
    else if (
180
0
        (major(st.st_dev) != proc_info->map_dmaj) ||
181
0
        (minor(st.st_dev) != proc_info->map_dmin) ||
182
0
        (st.st_ino != proc_info->map_ino))
183
0
    {
184
      // Wrong file, may have been replaced. Treat like missing.
185
0
      close(fd);
186
0
      fd = -1;
187
0
    }
188
0
    else if (st.st_size < proc_info->map_offset + block->size)
189
0
    {
190
      // Mapping extends past end of file. Treat like missing.
191
0
      close(fd);
192
0
      fd = -1;
193
0
    }
194
0
    else if ((st.st_mode & S_IFMT) != S_IFREG)
195
0
    {
196
      // Correct filesystem object, but not a regular file. Treat like
197
      // uninitialized mapping.
198
0
      close(fd);
199
0
      fd = -2;
200
0
    }
201
0
  }
202
203
0
  if (fd >= 0)
204
0
  {
205
0
    context->buffer = mmap(
206
0
        NULL,
207
0
        block->size,
208
0
        PROT_READ | PROT_WRITE,
209
0
        MAP_PRIVATE,
210
0
        fd,
211
0
        proc_info->map_offset);
212
0
    close(fd);
213
0
  }
214
0
  else
215
0
  {
216
0
    context->buffer = mmap(
217
0
        NULL,
218
0
        block->size,
219
0
        PROT_READ | PROT_WRITE,
220
0
        MAP_PRIVATE | MAP_ANONYMOUS,
221
0
        -1,
222
0
        0);
223
0
  }
224
225
0
  if (context->buffer != NULL)
226
0
  {
227
0
    context->buffer_size = block->size;
228
0
  }
229
0
  else
230
0
  {
231
0
    context->buffer_size = 0;
232
0
    goto _exit;
233
0
  }
234
235
  // If mapping can't be accessed through the filesystem, read everything from
236
  // target process VM.
237
0
  if (fd == -1)
238
0
  {
239
0
    if (pread(
240
0
            proc_info->mem_fd,
241
0
            (void*) context->buffer,
242
0
            block->size,
243
0
            block->base) == -1)
244
0
    {
245
0
      goto _exit;
246
0
    }
247
0
  }
248
0
  else
249
0
  {
250
0
    pagemap = calloc(block->size / page_size, sizeof(uint64_t));
251
0
    if (pagemap == NULL)
252
0
    {
253
0
      goto _exit;
254
0
    }
255
0
    if (pread(
256
0
            proc_info->pagemap_fd,
257
0
            pagemap,
258
0
            sizeof(uint64_t) * block->size / page_size,
259
0
            sizeof(uint64_t) * block->base / page_size) == -1)
260
0
    {
261
0
      goto _exit;
262
0
    }
263
264
0
    for (uint64_t i = 0; i < block->size / page_size; i++)
265
0
    {
266
0
      if (pagemap[i] >> 61 == 0)
267
0
      {
268
0
        continue;
269
0
      }
270
      // Overwrite our mapping if the page is present, file-backed, or
271
      // swap-backed and if it differs from our mapping.
272
0
      uint8_t buffer[page_size];
273
274
0
      if (pread(
275
0
              proc_info->mem_fd,
276
0
              buffer,
277
0
              page_size,
278
0
              block->base + i * page_size) == -1)
279
0
      {
280
0
        goto _exit;
281
0
      }
282
283
0
      if (memcmp(
284
0
              (void*) context->buffer + i * page_size,
285
0
              (void*) buffer,
286
0
              page_size) != 0)
287
0
      {
288
0
        memcpy(
289
0
            (void*) context->buffer + i * page_size, (void*) buffer, page_size);
290
0
      }
291
0
    }
292
0
  }
293
294
0
  result = context->buffer;
295
296
0
_exit:;
297
298
0
  if (pagemap)
299
0
  {
300
0
    free(pagemap);
301
0
    pagemap = NULL;
302
0
  }
303
304
0
  YR_DEBUG_FPRINTF(2, stderr, "- %s() {} = %p\n", __FUNCTION__, result);
305
306
0
  return result;
307
0
}
308
309
YR_API YR_MEMORY_BLOCK* yr_process_get_next_memory_block(
310
    YR_MEMORY_BLOCK_ITERATOR* iterator)
311
0
{
312
0
  YR_PROC_ITERATOR_CTX* context = (YR_PROC_ITERATOR_CTX*) iterator->context;
313
0
  YR_PROC_INFO* proc_info = (YR_PROC_INFO*) context->proc_info;
314
315
0
  char buffer[PATH_MAX];
316
0
  char perm[5];
317
318
0
  uint64_t begin, end;
319
0
  uint64_t current_begin = context->current_block.base +
320
0
                           context->current_block.size;
321
322
0
  uint64_t max_process_memory_chunk;
323
324
0
  yr_get_configuration_uint64(
325
0
      YR_CONFIG_MAX_PROCESS_MEMORY_CHUNK, &max_process_memory_chunk);
326
327
0
  iterator->last_error = ERROR_SUCCESS;
328
329
0
  if (proc_info->next_block_end <= current_begin)
330
0
  {
331
0
    int path_start, n = 0;
332
0
    char* p;
333
334
0
    while (fgets(buffer, sizeof(buffer), proc_info->maps) != NULL)
335
0
    {
336
      // locate the '\n' character
337
0
      p = strrchr(buffer, '\n');
338
      // If we haven't read the whole line, skip over the rest.
339
0
      if (p == NULL)
340
0
      {
341
0
        int c;
342
0
        do
343
0
        {
344
0
          c = fgetc(proc_info->maps);
345
0
        } while (c >= 0 && c != '\n');
346
0
      }
347
      // otherwise remove '\n' at the end of the line
348
0
      else
349
0
      {
350
0
        *p = '\0';
351
0
      }
352
353
      // Each row in /proc/$PID/maps describes a region of contiguous virtual
354
      // memory in a process or thread. Each row has the following fields:
355
      //
356
      // address           perms offset  dev   inode   pathname
357
      // 08048000-08056000 r-xp 00000000 03:0c 64593   /usr/sbin/gpm
358
      //
359
0
      n = sscanf(
360
0
          buffer,
361
0
          "%" SCNx64 "-%" SCNx64 " %4s "
362
0
          "%" SCNx64 " %" SCNx64 ":%" SCNx64 " %" SCNu64 " %n",
363
0
          &begin,
364
0
          &end,
365
0
          perm,
366
0
          &(proc_info->map_offset),
367
0
          &(proc_info->map_dmaj),
368
0
          &(proc_info->map_dmin),
369
0
          &(proc_info->map_ino),
370
0
          &path_start);
371
372
      // If the row was parsed correctly sscan must return 7.
373
0
      if (n == 7)
374
0
      {
375
        // skip the memory region that doesn't have read permission.
376
0
        if (perm[0] != 'r')
377
0
        {
378
0
          continue;
379
0
        }
380
        // path_start contains the offset within buffer where the path starts,
381
        // the path should start with /.
382
0
        if (buffer[path_start] == '/')
383
0
          strncpy(
384
0
              proc_info->map_path,
385
0
              buffer + path_start,
386
0
              sizeof(proc_info->map_path) - 1);
387
0
        else
388
0
          proc_info->map_path[0] = '\0';
389
0
        break;
390
0
      }
391
0
    }
392
393
0
    if (n == 7)
394
0
    {
395
0
      current_begin = begin;
396
0
      proc_info->next_block_end = end;
397
0
    }
398
0
    else
399
0
    {
400
0
      YR_DEBUG_FPRINTF(2, stderr, "+ %s() = NULL\n", __FUNCTION__);
401
0
      return NULL;
402
0
    }
403
0
  }
404
405
0
  context->current_block.base = current_begin;
406
0
  context->current_block.size = yr_min(
407
0
      proc_info->next_block_end - current_begin, max_process_memory_chunk);
408
409
0
  assert(context->current_block.size > 0);
410
411
0
  YR_DEBUG_FPRINTF(
412
0
      2,
413
0
      stderr,
414
0
      "- %s() {} = %p // .base=0x%" PRIx64 " .size=%" PRIu64 "\n",
415
0
      __FUNCTION__,
416
0
      context->current_block,
417
0
      context->current_block.base,
418
0
      context->current_block.size);
419
420
0
  return &context->current_block;
421
0
}
422
423
YR_API YR_MEMORY_BLOCK* yr_process_get_first_memory_block(
424
    YR_MEMORY_BLOCK_ITERATOR* iterator)
425
0
{
426
0
  YR_DEBUG_FPRINTF(2, stderr, "+ %s() {\n", __FUNCTION__);
427
428
0
  YR_MEMORY_BLOCK* result = NULL;
429
0
  YR_PROC_ITERATOR_CTX* context = (YR_PROC_ITERATOR_CTX*) iterator->context;
430
0
  YR_PROC_INFO* proc_info = (YR_PROC_INFO*) context->proc_info;
431
432
0
  if (fseek(proc_info->maps, 0, SEEK_SET) != 0)
433
0
  {
434
0
    result = NULL;
435
0
    goto _exit;
436
0
  }
437
438
0
  proc_info->next_block_end = 0;
439
440
0
  result = yr_process_get_next_memory_block(iterator);
441
442
0
_exit:
443
444
0
  if (result == NULL)
445
0
    iterator->last_error = ERROR_COULD_NOT_READ_PROCESS_MEMORY;
446
447
0
  YR_DEBUG_FPRINTF(2, stderr, "} = %p // %s()\n", result, __FUNCTION__);
448
449
0
  return result;
450
0
}
451
452
#endif