new upstream release (3.3.0); modify package compatibility for Stretch
[ossec-hids.git] / src / rootcheck / check_rc_sys.c
1 /* Copyright (C) 2009 Trend Micro Inc.
2  * All right reserved.
3  *
4  * This program is a free software; you can redistribute it
5  * and/or modify it under the terms of the GNU General Public
6  * License (version 2) as published by the FSF - Free Software
7  * Foundation
8  */
9
10 #include "shared.h"
11 #include "rootcheck.h"
12
13 /* Prototypes */
14 static int read_sys_file(const char *file_name, int do_read);
15 static int read_sys_dir(const char *dir_name, int do_read);
16
17 /* Global variables */
18 static int   _sys_errors;
19 static int   _sys_total;
20 static dev_t did;
21 static FILE *_wx;
22 static FILE *_ww;
23 static FILE *_suid;
24
25
26 static int read_sys_file(const char *file_name, int do_read)
27 {
28     struct stat statbuf;
29
30     _sys_total++;
31
32 #ifdef WIN32
33     /* Check for NTFS ADS on Windows */
34     os_check_ads(file_name);
35 #endif
36     if (lstat(file_name, &statbuf) < 0) {
37 #ifndef WIN32
38         char op_msg[OS_SIZE_1024 + 1];
39         snprintf(op_msg, OS_SIZE_1024, "Anomaly detected in file '%s'. "
40                  "Hidden from stats, but showing up on readdir. "
41                  "Possible kernel level rootkit.",
42                  file_name);
43         notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
44         _sys_errors++;
45 #endif
46         return (-1);
47     }
48
49     /* If directory, read the directory */
50     else if (S_ISDIR(statbuf.st_mode)) {
51         /* Make Darwin happy. For some reason,
52          * when I read /dev/fd, it goes forever on
53          * /dev/fd5, /dev/fd6, etc.. weird
54          */
55         if (strstr(file_name, "/dev/fd") != NULL) {
56             return (0);
57         }
58
59         /* Ignore the /proc directory (it has size 0) */
60         if (statbuf.st_size == 0) {
61             return (0);
62         }
63
64         return (read_sys_dir(file_name, do_read));
65     }
66
67     /* Check if the size from stats is the same as when we read the file */
68     if (S_ISREG(statbuf.st_mode) && do_read) {
69         char buf[OS_SIZE_1024];
70         int fd;
71         ssize_t nr;
72         long int total = 0;
73
74         fd = open(file_name, O_RDONLY, 0);
75
76         /* It may not necessarily open */
77         if (fd >= 0) {
78             while ((nr = read(fd, buf, sizeof(buf))) > 0) {
79                 total += nr;
80             }
81             close(fd);
82
83             if (strcmp(file_name, "/dev/bus/usb/.usbfs/devices") == 0) {
84                 /* Ignore .usbfs/devices */
85             } else if (total != statbuf.st_size) {
86                 struct stat statbuf2;
87
88                 if ((lstat(file_name, &statbuf2) == 0) &&
89                         (total != statbuf2.st_size) &&
90                         (statbuf.st_size == statbuf2.st_size)) {
91                     char op_msg[OS_SIZE_1024 + 1];
92                     snprintf(op_msg, OS_SIZE_1024, "Anomaly detected in file "
93                              "'%s'. File size doesn't match what we found. "
94                              "Possible kernel level rootkit.",
95                              file_name);
96                     notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
97                     _sys_errors++;
98                 }
99             }
100         }
101     }
102
103     /* If has OTHER write and exec permission, alert */
104 #ifndef WIN32
105     if ((statbuf.st_mode & S_IWOTH) == S_IWOTH && S_ISREG(statbuf.st_mode)) {
106         if ((statbuf.st_mode & S_IXUSR) == S_IXUSR) {
107             if (_wx) {
108                 fprintf(_wx, "%s\n", file_name);
109             }
110
111             _sys_errors++;
112         } else {
113             if (_ww) {
114                 fprintf(_ww, "%s\n", file_name);
115             }
116         }
117
118         if (statbuf.st_uid == 0) {
119             char op_msg[OS_SIZE_1024 + 1];
120 #ifdef OSSECHIDS
121             snprintf(op_msg, OS_SIZE_1024, "File '%s' is owned by root "
122                      "and has write permissions to anyone.", file_name);
123 #else
124             snprintf(op_msg, OS_SIZE_1024, "File '%s' is: \n"
125                      "          - owned by root,\n"
126                      "          - has write permissions to anyone.",
127                      file_name);
128 #endif
129             notify_rk(ALERT_SYSTEM_CRIT, op_msg);
130
131         }
132         _sys_errors++;
133     } else if ((statbuf.st_mode & S_ISUID) == S_ISUID) {
134         if (_suid) {
135             fprintf(_suid, "%s\n", file_name);
136         }
137     }
138 #endif /* WIN32 */
139     return (0);
140 }
141
142 static int read_sys_dir(const char *dir_name, int do_read)
143 {
144     int i = 0;
145     unsigned int entry_count = 0;
146     int did_changed = 0;
147     DIR *dp;
148     struct dirent *entry;
149     struct stat statbuf;
150     short is_nfs;
151     short skip_fs;
152
153 #ifndef WIN32
154     const char *(dirs_to_doread[]) = { "/bin", "/sbin", "/usr/bin",
155                                        "/usr/sbin", "/dev", "/etc",
156                                        "/boot", NULL
157                                      };
158 #endif
159
160     if ((dir_name == NULL) || (strlen(dir_name) > PATH_MAX)) {
161         merror("%s: Invalid directory given.", ARGV0);
162         return (-1);
163     }
164
165     /* Ignore user-supplied list */
166     if (rootcheck.ignore) {
167         while (rootcheck.ignore[i]) {
168             if (strcmp(dir_name, rootcheck.ignore[i]) == 0) {
169                 return (1);
170             }
171             i++;
172         }
173         i = 0;
174     }
175
176     /* Should we check for NFS? */
177     if(rootcheck.skip_nfs)
178     {
179         is_nfs = IsNFS(dir_name);
180         if(is_nfs != 0)
181         {
182             // Error will be -1, and 1 means skipped
183             return(is_nfs);
184         }
185     }
186
187     /* Getting the number of nodes. The total number on opendir
188      * must be the same
189      */
190     if(lstat(dir_name, &statbuf) < 0)
191     {
192         return(-1);
193     }
194
195     /* Current device id */
196     if (did != statbuf.st_dev) {
197         if (did != 0) {
198             did_changed = 1;
199         }
200         did = statbuf.st_dev;
201     }
202
203     if (!S_ISDIR(statbuf.st_mode)) {
204         return (-1);
205     }
206
207 #ifndef WIN32
208     /* Check if the do_read is valid for this directory */
209     while (dirs_to_doread[i]) {
210         if (strcmp(dir_name, dirs_to_doread[i]) == 0) {
211             do_read = 1;
212             break;
213         }
214         i++;
215     }
216 #else
217     do_read = 0;
218 #endif
219
220     /* Open the directory */
221     dp = opendir(dir_name);
222     if (!dp) {
223         if ((strcmp(dir_name, "") == 0) &&
224                 (dp = opendir("/"))) {
225             /* ok */
226         } else {
227             return (-1);
228         }
229     }
230
231     /* Read every entry in the directory */
232     while ((entry = readdir(dp)) != NULL) {
233         char f_name[PATH_MAX + 2];
234         struct stat statbuf_local;
235
236         /* Ignore . and ..  */
237         if ((strcmp(entry->d_name, ".") == 0) ||
238                 (strcmp(entry->d_name, "..") == 0)) {
239             entry_count++;
240             continue;
241         }
242
243         /* Create new file + path string */
244         if (strcmp(dir_name, "/") == 0) {
245             snprintf(f_name, PATH_MAX + 1, "/%s", entry->d_name);
246         } else {
247             snprintf(f_name, PATH_MAX + 1, "%s/%s", dir_name, entry->d_name);
248         }
249
250         /* Check if file is a directory */
251         if (lstat(f_name, &statbuf_local) == 0) {
252             /* On all the systems except Darwin, the
253              * link count is only increased on directories
254              */
255 #ifndef Darwin
256             if (S_ISDIR(statbuf_local.st_mode))
257 #else
258             if (S_ISDIR(statbuf_local.st_mode) ||
259                     S_ISREG(statbuf_local.st_mode)
260             /* No S_ISLNK on Windows */
261 #ifndef WIN32
262                     || S_ISLNK(statbuf_local.st_mode)
263 #endif
264                     )
265 #endif
266             {
267                 entry_count++;
268             }
269         }
270
271         /* Check every file against the rootkit database */
272         for (i = 0; i <= rk_sys_count; i++) {
273             if (!rk_sys_file[i]) {
274                 break;
275             }
276
277             if (strcmp(rk_sys_file[i], entry->d_name) == 0) {
278                 char op_msg[OS_SIZE_1024 + 1];
279
280                 _sys_errors++;
281                 snprintf(op_msg, OS_SIZE_1024, "Rootkit '%s' detected "
282                          "by the presence of file '%s/%s'.",
283                          rk_sys_name[i], dir_name, rk_sys_file[i]);
284
285                 notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
286             }
287         }
288
289         /* Ignore the /proc and /sys filesystems */
290         if ((strcmp(f_name, "/proc") == 0) || (strcmp(f_name, "/sys") == 0)) {
291             continue;
292         }
293
294         read_sys_file(f_name, do_read);
295     }
296
297     /* skip further test because the FS cant deliver the stats (btrfs link count always is 1) */
298     skip_fs = skipFS(dir_name);
299     if(skip_fs != 0)
300     {
301         // Error will be -1, and 1 means skipped
302         closedir(dp);
303         return(0);
304     }
305
306     /* Entry count for directory different than the actual
307      * link count from stats
308      */
309     if ((entry_count != (unsigned) statbuf.st_nlink) &&
310             ((did_changed == 0) || ((entry_count + 1) != (unsigned) statbuf.st_nlink))) {
311 #ifndef WIN32
312         struct stat statbuf2;
313         char op_msg[OS_SIZE_1024 + 1];
314
315         if ((lstat(dir_name, &statbuf2) == 0) &&
316                 (statbuf2.st_nlink != entry_count)) {
317             snprintf(op_msg, OS_SIZE_1024, "Files hidden inside directory "
318                      "'%s'. Link count does not match number of files "
319                      "(%d,%d).",
320                      dir_name, entry_count, (int)statbuf.st_nlink);
321
322             /* Solaris /boot is terrible :) */
323 #ifdef SOLARIS
324             if (strncmp(dir_name, "/boot", strlen("/boot")) != 0) {
325                 notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
326                 _sys_errors++;
327             }
328 #elif defined(Darwin) || defined(FreeBSD)
329             if (strncmp(dir_name, "/dev", strlen("/dev")) != 0) {
330                 notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
331                 _sys_errors++;
332             }
333 #else
334             notify_rk(ALERT_ROOTKIT_FOUND, op_msg);
335             _sys_errors++;
336 #endif
337         }
338 #endif /* WIN32 */
339     }
340
341     closedir(dp);
342
343     return (0);
344 }
345
346 /* Scan the whole filesystem looking for possible issues */
347 void check_rc_sys(const char *basedir)
348 {
349     char file_path[OS_SIZE_1024 + 1];
350
351     debug1("%s: DEBUG: Starting on check_rc_sys", ARGV0);
352
353     _sys_errors = 0;
354     _sys_total = 0;
355     did = 0; /* device id */
356
357     snprintf(file_path, OS_SIZE_1024, "%s", basedir);
358
359     /* Open output files */
360     if (rootcheck.notify != QUEUE) {
361         _wx = fopen("rootcheck-rw-rw-rw-.txt", "w");
362         _ww = fopen("rootcheck-rwxrwxrwx.txt", "w");
363         _suid = fopen("rootcheck-suid-files.txt", "w");
364     } else {
365         _wx = NULL;
366         _ww = NULL;
367         _suid = NULL;
368     }
369
370     if (rootcheck.scanall) {
371         /* Scan the whole file system -- may be slow */
372 #ifndef WIN32
373         snprintf(file_path, 3, "%s", "/");
374 #endif
375         read_sys_dir(file_path, rootcheck.readall);
376     } else {
377         /* Scan only specific directories */
378         int _i;
379 #ifndef WIN32
380         const char *(dirs_to_scan[]) = {"/bin", "/sbin", "/usr/bin",
381                                         "/usr/sbin", "/dev", "/lib",
382                                         "/etc", "/root", "/var/log",
383                                         "/var/mail", "/var/lib", "/var/www",
384                                         "/usr/lib", "/usr/include",
385                                         "/tmp", "/boot", "/usr/local",
386                                         "/var/tmp", "/sys", NULL
387                                        };
388
389 #else
390         const char *(dirs_to_scan[]) = {"C:\\WINDOWS", "C:\\Program Files", NULL};
391 #endif
392
393         _i = 0;
394         while (dirs_to_scan[_i] != NULL) {
395 #ifndef WIN32
396             snprintf(file_path, OS_SIZE_1024, "%s%s",
397                      basedir,
398                      dirs_to_scan[_i]);
399             read_sys_dir(file_path, rootcheck.readall);
400
401 #else
402             read_sys_dir(dirs_to_scan[_i], rootcheck.readall);
403 #endif
404
405             _i++;
406         }
407     }
408
409     if (_sys_errors == 0) {
410         char op_msg[OS_SIZE_1024 + 1];
411         snprintf(op_msg, OS_SIZE_1024, "No problem found on the system."
412                  " Analyzed %d files.", _sys_total);
413         notify_rk(ALERT_OK, op_msg);
414     }
415
416     else if (_wx && _ww && _suid) {
417         char op_msg[OS_SIZE_1024 + 1];
418         snprintf(op_msg, OS_SIZE_1024, "Check the following files for more "
419                  "information:\n%s%s%s",
420                  (ftell(_wx) == 0) ? "" :
421                  "       rootcheck-rw-rw-rw-.txt (list of world writable files)\n",
422                  (ftell(_ww) == 0) ? "" :
423                  "       rootcheck-rwxrwxrwx.txt (list of world writtable/executable files)\n",
424                  (ftell(_suid) == 0) ? "" :
425                  "       rootcheck-suid-files.txt (list of suid files)");
426
427         notify_rk(ALERT_SYSTEM_ERR, op_msg);
428     }
429
430     if (_wx) {
431         if (ftell(_wx) == 0) {
432             unlink("rootcheck-rw-rw-rw-.txt");
433         }
434         fclose(_wx);
435     }
436
437     if (_ww) {
438         if (ftell(_ww) == 0) {
439             unlink("rootcheck-rwxrwxrwx.txt");
440         }
441         fclose(_ww);
442     }
443
444     if (_suid) {
445         if (ftell(_suid) == 0) {
446             unlink("rootcheck-suid-files.txt");
447         }
448         fclose(_suid);
449     }
450
451     return;
452 }
453