1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * Implementation of the "scan file" interface 30 */ 31 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <unistd.h> 35 #include <string.h> 36 #include <errno.h> 37 #include <syslog.h> 38 #include <sys/types.h> 39 #include <fcntl.h> 40 #include <bsm/adt.h> 41 #include <bsm/adt_event.h> 42 #include <pthread.h> 43 44 #include "vs_incl.h" 45 46 /* 47 * vs_svc_nodes - table of scan requests and their thread id and 48 * scan engine context. 49 * The table is sized by the value passed to vs_svc_init. This 50 * value is obtained from the kernel and represents the maximum 51 * request idx that the kernel will request vscand to process. 52 * The table is indexed by the vsr_idx value passed in 53 * the scan request - always non-zero. This value is also the index 54 * into the kernel scan request table and identifies the instance of 55 * the driver being used to access file data for the scan. Although 56 * this is of no consequence here, it is useful information for debug. 57 * 58 * When a scan request is received a response is sent indicating 59 * one of the following: 60 * VS_STATUS_ERROR - an error occurred 61 * VS_STATUS_NO_SCAN - no scan is required 62 * VS_STATUS_SCANNING - request has been queued for async processing 63 * 64 * If the scan is required (VS_STATUS_SCANNING) a thread is created 65 * to perform the scan. It's tid is saved in vs_svc_nodes. 66 * 67 * In the case of SHUTDOWN, vs_terminate requests that all scan 68 * engine connections be closed, thus termintaing any in-progress 69 * scans, then awaits completion of all scanning threads as identified 70 * in vs_svc_nodes. 71 */ 72 73 typedef struct vs_svc_node { 74 pthread_t vsn_tid; 75 vs_scan_req_t vsn_req; 76 vs_eng_ctx_t vsn_eng; 77 } vs_svc_node_t; 78 79 static vs_svc_node_t *vs_svc_nodes; 80 static uint32_t vs_svc_max_node; /* max idx into vs_svc_nodes */ 81 static pthread_mutex_t vs_svc_mutex = PTHREAD_MUTEX_INITIALIZER; 82 83 84 /* local functions */ 85 static void *vs_svc_async_scan(void *); 86 static int vs_svc_scan_file(vs_svc_node_t *, vs_scanstamp_t *); 87 static void vs_svc_vlog(char *, vs_result_t *); 88 static void vs_svc_audit(char *, vs_result_t *); 89 90 91 /* 92 * vs_svc_init, vs_svc_fini 93 * 94 * Invoked on daemon load and unload 95 */ 96 int 97 vs_svc_init(uint32_t max_req) 98 { 99 vs_svc_max_node = max_req; 100 vs_svc_nodes = (vs_svc_node_t *) 101 calloc(max_req + 1, sizeof (vs_svc_node_t)); 102 103 return (vs_svc_nodes == NULL ? -1 : 0); 104 } 105 106 void 107 vs_svc_fini() 108 { 109 if (vs_svc_nodes) 110 free(vs_svc_nodes); 111 } 112 113 114 /* 115 * vs_svc_terminate 116 * 117 * Close all scan engine connections to terminate in-progress scan 118 * requests, and wait for all threads in vs_svc_nodes to complete 119 */ 120 void 121 vs_svc_terminate() 122 { 123 int i; 124 pthread_t tid; 125 126 /* close connections to abort requests */ 127 vs_eng_close_connections(); 128 129 /* wait for threads */ 130 for (i = 1; i <= vs_svc_max_node; i++) { 131 132 (void) pthread_mutex_lock(&vs_svc_mutex); 133 tid = vs_svc_nodes[i].vsn_tid; 134 (void) pthread_mutex_unlock(&vs_svc_mutex); 135 136 if (tid != 0) 137 (void) pthread_join(tid, NULL); 138 } 139 } 140 141 142 /* 143 * vs_svc_queue_scan_req 144 * 145 * Determine if the file needs to be scanned - either it has 146 * been modified or its scanstamp is not current. 147 * Initiate a thread to process the request, saving the tid 148 * in vs_svc_nodes[idx].vsn_tid, where idx is the vsr_idx passed in 149 * the scan request. 150 * 151 * Returns: VS_STATUS_ERROR - error 152 * VS_STATUS_NO_SCAN - no scan required 153 * VS_STATUS_SCANNING - async scan initiated 154 */ 155 int 156 vs_svc_queue_scan_req(vs_scan_req_t *req) 157 { 158 pthread_t tid; 159 vs_svc_node_t *node; 160 161 /* No scan if file quarantined */ 162 if (req->vsr_quarantined) 163 return (VS_STATUS_NO_SCAN); 164 165 /* No scan if file not modified AND scanstamp is current */ 166 if ((req->vsr_modified == 0) && 167 vs_eng_scanstamp_current(req->vsr_scanstamp)) { 168 return (VS_STATUS_NO_SCAN); 169 } 170 171 /* scan required */ 172 node = &(vs_svc_nodes[req->vsr_idx]); 173 174 (void) pthread_mutex_lock(&vs_svc_mutex); 175 if ((node->vsn_tid != 0) || (req->vsr_idx > vs_svc_max_node)) { 176 (void) pthread_mutex_unlock(&vs_svc_mutex); 177 return (VS_STATUS_ERROR); 178 } 179 180 node->vsn_req = *req; 181 182 if (pthread_create(&tid, NULL, vs_svc_async_scan, (void *)node) != 0) { 183 (void) pthread_mutex_unlock(&vs_svc_mutex); 184 return (VS_STATUS_ERROR); 185 } 186 187 node->vsn_tid = tid; 188 (void) pthread_mutex_unlock(&vs_svc_mutex); 189 190 return (VS_STATUS_SCANNING); 191 } 192 193 194 /* 195 * vs_svc_async_scan 196 * 197 * Initialize response structure, invoke vs_svc_scan_file to 198 * perform the scan, then send the result to the kernel. 199 */ 200 static void * 201 vs_svc_async_scan(void *arg) 202 { 203 vs_svc_node_t *node = (vs_svc_node_t *)arg; 204 vs_scan_req_t *scan_req = &(node->vsn_req); 205 vs_scan_rsp_t scan_rsp; 206 207 scan_rsp.vsr_idx = scan_req->vsr_idx; 208 scan_rsp.vsr_seqnum = scan_req->vsr_seqnum; 209 scan_rsp.vsr_result = vs_svc_scan_file(node, &scan_rsp.vsr_scanstamp); 210 211 /* clear node and send async response to kernel */ 212 (void) pthread_mutex_lock(&vs_svc_mutex); 213 (void) memset(node, 0, sizeof (vs_svc_node_t)); 214 (void) pthread_mutex_unlock(&vs_svc_mutex); 215 216 (void) vscand_kernel_result(&scan_rsp); 217 218 return (NULL); 219 } 220 221 222 /* 223 * vs_svc_scan_file 224 * 225 * vs_svc_scan_file is responsible for: 226 * - obtaining & releasing a scan engine connection 227 * - invoking the scan engine interface code to do the scan 228 * - retrying a failed scan (up to VS_MAX_RETRY times) 229 * - updating scan statistics 230 * - logging virus information 231 * 232 * 233 * Returns: 234 * VS_STATUS_NO_SCAN - scan not reqd; daemon shutting down 235 * VS_STATUS_CLEAN - scan success. File clean. 236 * new scanstamp returned in scanstamp param. 237 * VS_STATUS_INFECTED - scan success. File infected. 238 * VS_STATUS_ERROR - scan failure either in vscand or scan engine. 239 */ 240 static int 241 vs_svc_scan_file(vs_svc_node_t *node, vs_scanstamp_t *scanstamp) 242 { 243 char devname[MAXPATHLEN]; 244 int flags = 0; 245 int retries; 246 vs_result_t result; 247 vs_scan_req_t *req = &(node->vsn_req); 248 vs_eng_ctx_t *eng = &(node->vsn_eng); 249 250 (void) snprintf(devname, MAXPATHLEN, "%s%d", VS_DRV_PATH, req->vsr_idx); 251 252 /* initialize response scanstamp to current scanstamp value */ 253 (void) strlcpy(*scanstamp, req->vsr_scanstamp, sizeof (vs_scanstamp_t)); 254 255 (void) memset(&result, 0, sizeof (vs_result_t)); 256 result.vsr_rc = VS_RESULT_UNDEFINED; 257 258 for (retries = 0; retries <= VS_MAX_RETRY; retries++) { 259 /* get engine connection */ 260 if (vs_eng_get(eng, (retries != 0)) != 0) { 261 result.vsr_rc = VS_RESULT_ERROR; 262 continue; 263 } 264 265 /* shutdown could occur while waiting for engine connection */ 266 if (vscand_get_state() == VS_STATE_SHUTDOWN) { 267 vs_eng_release(eng); 268 return (VS_STATUS_NO_SCAN); 269 } 270 271 /* scan file */ 272 (void) vs_icap_scan_file(eng, devname, req->vsr_path, 273 req->vsr_size, flags, &result); 274 275 /* if no error, clear error state on engine and break */ 276 if ((result.vsr_rc != VS_RESULT_SE_ERROR) && 277 (result.vsr_rc != VS_RESULT_ERROR)) { 278 vs_eng_set_error(eng, 0); 279 vs_eng_release(eng); 280 break; 281 } 282 283 /* treat error on shutdown as scan not required */ 284 if (vscand_get_state() == VS_STATE_SHUTDOWN) { 285 vs_eng_release(eng); 286 return (VS_STATUS_NO_SCAN); 287 } 288 289 /* set engine's error state and update engine stats */ 290 if (result.vsr_rc == VS_RESULT_SE_ERROR) 291 vs_eng_set_error(eng, 1); 292 293 vs_eng_release(eng); 294 } 295 296 vs_stats_set(result.vsr_rc); 297 298 /* 299 * VS_RESULT_CLEANED - file infected, cleaned data available 300 * VS_RESULT_FORBIDDEN - file infected, no cleaned data 301 * Log virus, write audit record and return INFECTED status 302 */ 303 if (result.vsr_rc == VS_RESULT_CLEANED || 304 result.vsr_rc == VS_RESULT_FORBIDDEN) { 305 vs_svc_vlog(req->vsr_path, &result); 306 vs_svc_audit(req->vsr_path, &result); 307 return (VS_STATUS_INFECTED); 308 } 309 310 /* VS_RESULT_CLEAN - Set the scanstamp and return CLEAN status */ 311 if (result.vsr_rc == VS_RESULT_CLEAN) { 312 (void) strlcpy(*scanstamp, result.vsr_scanstamp, 313 sizeof (vs_scanstamp_t)); 314 return (VS_STATUS_CLEAN); 315 } 316 317 return (VS_STATUS_ERROR); 318 } 319 320 321 /* 322 * vs_svc_vlog 323 * 324 * log details of infections detected in syslig 325 * If virus log is configured log details there too 326 */ 327 static void 328 vs_svc_vlog(char *filepath, vs_result_t *result) 329 { 330 FILE *fp = NULL; 331 time_t sec; 332 struct tm *timestamp; 333 char timebuf[18]; /* MM/DD/YY hh:mm:ss */ 334 int i; 335 char *log; 336 337 /* syslog */ 338 if (result->vsr_nviolations == 0) { 339 syslog(LOG_WARNING, "quarantine %s\n", filepath); 340 } else { 341 for (i = 0; i < result->vsr_nviolations; i++) { 342 syslog(LOG_WARNING, "quarantine %s %d - %s\n", 343 filepath, 344 result->vsr_vrec[i].vr_id, 345 result->vsr_vrec[i].vr_desc); 346 } 347 } 348 349 /* log file */ 350 if (((log = vscand_viruslog()) == NULL) || 351 ((fp = fopen(log, "a")) == NULL)) { 352 return; 353 } 354 355 (void) time(&sec); 356 timestamp = localtime(&sec); 357 (void) strftime(timebuf, sizeof (timebuf), "%D %T", timestamp); 358 359 if (result->vsr_nviolations == 0) { 360 (void) fprintf(fp, "%s quarantine %d[%s]\n", 361 timebuf, strlen(filepath), filepath); 362 } else { 363 for (i = 0; i < result->vsr_nviolations; i++) { 364 (void) fprintf(fp, "%s quarantine %d[%s] %d - %d[%s]\n", 365 timebuf, strlen(filepath), filepath, 366 result->vsr_vrec[i].vr_id, 367 strlen(result->vsr_vrec[i].vr_desc), 368 result->vsr_vrec[i].vr_desc); 369 } 370 } 371 372 (void) fclose(fp); 373 } 374 375 376 /* 377 * vs_svc_audit 378 * 379 * Generate AUE_vscan_quarantine audit record containing name 380 * of infected file, and violation details if available. 381 */ 382 static void 383 vs_svc_audit(char *filepath, vs_result_t *result) 384 { 385 int i; 386 char *violations[VS_MAX_VIOLATIONS]; 387 char data[VS_MAX_VIOLATIONS][VS_DESCRIPTION_MAX]; 388 adt_session_data_t *ah; 389 adt_termid_t *p_tid; 390 adt_event_data_t *event; 391 392 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA)) { 393 syslog(LOG_AUTH | LOG_ALERT, "adt_start_session: %m"); 394 return; 395 } 396 397 if (adt_load_ttyname("/dev/console", &p_tid) != 0) { 398 syslog(LOG_AUTH | LOG_ALERT, 399 "adt_load_ttyname(/dev/console): %m"); 400 return; 401 } 402 403 if (adt_set_user(ah, ADT_NO_ATTRIB, ADT_NO_ATTRIB, ADT_NO_ATTRIB, 404 ADT_NO_ATTRIB, p_tid, ADT_NEW) != 0) { 405 syslog(LOG_AUTH | LOG_ALERT, "adt_set_user(ADT_NO_ATTRIB): %m"); 406 (void) adt_end_session(ah); 407 return; 408 } 409 410 if ((event = adt_alloc_event(ah, ADT_vscan_quarantine)) == NULL) { 411 syslog(LOG_AUTH | LOG_ALERT, 412 "adt_alloc_event(ADT_vscan_quarantine)): %m"); 413 (void) adt_end_session(ah); 414 return; 415 } 416 417 /* populate vscan audit event */ 418 event->adt_vscan_quarantine.file = filepath; 419 for (i = 0; i < result->vsr_nviolations; i++) { 420 (void) snprintf(data[i], VS_DESCRIPTION_MAX, "%d - %s", 421 result->vsr_vrec[i].vr_id, result->vsr_vrec[i].vr_desc); 422 violations[i] = data[i]; 423 } 424 425 event->adt_vscan_quarantine.violations = (char **)violations; 426 event->adt_vscan_quarantine.nviolations = result->vsr_nviolations; 427 428 if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS)) 429 syslog(LOG_AUTH | LOG_ALERT, "adt_put_event: %m"); 430 431 adt_free_event(event); 432 (void) adt_end_session(ah); 433 } 434