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