1 /**
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #ifdef USE_GWP_ASAN
17 #define _GNU_SOURCE
18
19 #include <fcntl.h>
20 #include <unistd.h>
21 #include <string.h>
22 #include <sys/random.h>
23
24 #include "gwp_asan.h"
25
26 #include "musl_log.h"
27 #include "musl_malloc.h"
28 #include "pthread.h"
29 #include "pthread_impl.h"
30
31 #ifdef OHOS_ENABLE_PARAMETER
32 #include "sys_param.h"
33 #endif
34
35 #define MAX_SIMULTANEOUS_ALLOCATIONS 32
36 #define SAMPLE_RATE 2500
37 #define GWP_ASAN_NAME_LEN 256
38 #define GWP_ASAN_PREDICT_TRUE(exp) __builtin_expect((exp) != 0, 1)
39 #define GWP_ASAN_PREDICT_FALSE(exp) __builtin_expect((exp) != 0, 0)
40 #define GWP_ASAN_LOGD(...) // change it to MUSL_LOGD to get gwp_asan debug log.
41 #define GWP_ASAN_NO_ADDRESS __attribute__((no_sanitize("address", "hwaddress")))
42
43 #if defined(__aarch64__)
44 #define REG_AARCH64_X29 29
get_pc(ucontext_t * context)45 static size_t get_pc(ucontext_t *context)
46 {
47 return context->uc_mcontext.pc;
48 }
49
get_frame_pointer(ucontext_t * context)50 static size_t get_frame_pointer(ucontext_t *context)
51 {
52 return context->uc_mcontext.regs[REG_AARCH64_X29];
53 }
54 #else
get_pc(ucontext_t * context)55 static size_t get_pc(ucontext_t *context)
56 {
57 GWP_ASAN_LOGD("[gwp_asan] Unsupported platform");
58 return 0;
59 }
60
get_frame_pointer(ucontext_t * context)61 static size_t get_frame_pointer(ucontext_t *context)
62 {
63 GWP_ASAN_LOGD("[gwp_asan] Unsupported platform");
64 return 0;
65 }
66 #endif
67
68 static bool gwp_asan_initialized = false;
69 static uint8_t process_sample_rate = 128;
70 static uint8_t force_sample_alloctor = 0;
71 static uint8_t previous_random_value = 0;
72
73 #ifndef _GNU_SOURCE
74 typedef struct {
75 const char *dli_fname;
76 void *dli_fbase;
77 const char *dli_sname;
78 void *dli_saddr;
79 } Dl_info;
80 #endif
81
82 // C interfaces of gwp_asan provided by LLVM side.
83 extern void init_gwp_asan(void *init_options);
84 extern void* gwp_asan_malloc(size_t bytes);
85 extern void gwp_asan_free(void *mem);
86 extern bool gwp_asan_should_sample();
87 extern bool gwp_asan_pointer_is_mine(void *mem);
88 extern bool gwp_asan_has_free_mem();
89 extern size_t gwp_asan_get_size(void *mem);
90 extern void gwp_asan_disable();
91 extern void gwp_asan_enable();
92 extern void gwp_asan_iterate(void *base, size_t size,
93 void (*callback)(void* base, size_t size, void *arg), void *arg);
94
95
96 extern int dladdr(const void *addr, Dl_info *info);
97
get_process_short_name(char * buf,size_t length)98 static char *get_process_short_name(char *buf, size_t length)
99 {
100 char *app = NULL;
101 int fd = open("/proc/self/cmdline", O_RDONLY);
102 if (fd != -1) {
103 ssize_t ret = read(fd, buf, length - 1);
104 if (ret != -1) {
105 buf[ret] = 0;
106 app = strrchr(buf, '/');
107 if (app) {
108 app++;
109 } else {
110 app = buf;
111 }
112 char *app_end = strchr(app, ':');
113 if (app_end) {
114 *app_end = 0;
115 }
116 }
117 close(fd);
118 }
119 return app;
120 }
121
is_gwp_asan_disable()122 bool is_gwp_asan_disable()
123 {
124 char para_name[GWP_ASAN_NAME_LEN] = "gwp_asan.disable.all";
125 static CachedHandle para_handler = NULL;
126 if (para_handler == NULL) {
127 para_handler = CachedParameterCreate(para_name, "false");
128 }
129 const char *para_value = CachedParameterGet(para_handler);
130 if (para_value != NULL && strcmp(para_value, "true") == 0) {
131 return true;
132 }
133 return false;
134 }
135
force_sample_process_by_env()136 bool force_sample_process_by_env()
137 {
138 char buf[GWP_ASAN_NAME_LEN];
139 char *path = get_process_short_name(buf, GWP_ASAN_NAME_LEN);
140 if (!path) {
141 return false;
142 }
143 char para_name[GWP_ASAN_NAME_LEN] = "gwp_asan.enable.app.";
144 strcat(para_name, path);
145 static CachedHandle app_enable_handle = NULL;
146 if (app_enable_handle == NULL) {
147 app_enable_handle = CachedParameterCreate(para_name, "false");
148 }
149 const char *param_value = CachedParameterGet(app_enable_handle);
150 if (param_value != NULL) {
151 if (strcmp(param_value, "true") == 0) {
152 return true;
153 }
154 }
155 return false;
156 }
157
force_sample_alloctor_by_env()158 bool force_sample_alloctor_by_env()
159 {
160 char para_name[GWP_ASAN_NAME_LEN] = "gwp_asan.sample.all";
161 static CachedHandle para_handler = NULL;
162 if (para_handler == NULL) {
163 para_handler = CachedParameterCreate(para_name, "false");
164 }
165 const char *para_value = CachedParameterGet(para_handler);
166 if (para_value != NULL && strcmp(para_value, "true") == 0) {
167 force_sample_alloctor = 1;
168 return true;
169 }
170 return false;
171 }
172
should_sample_process()173 bool should_sample_process()
174 {
175 #ifdef OHOS_ENABLE_PARAMETER
176 if (force_sample_process_by_env()) {
177 return true;
178 }
179 #endif
180
181 uint8_t random_value;
182 // If getting a random number using a non-blocking fails, the random value is incremented.
183 if (getrandom(&random_value, sizeof(random_value), GRND_RANDOM | GRND_NONBLOCK) == -1) {
184 random_value = previous_random_value + 1;
185 previous_random_value = random_value;
186 }
187
188 return (random_value % process_sample_rate) == 0 ? true : false;
189 }
190
191 #define ASAN_LOG_LIB "libasan_logger.z.so"
192 static void (*WriteSanitizerLog)(char*, size_t, char*);
193 static void* handle = NULL;
194 static bool try_load_asan_logger = false;
195 static pthread_mutex_t gwpasan_mutex = PTHREAD_MUTEX_INITIALIZER;
196
gwp_asan_printf(const char * fmt,...)197 void gwp_asan_printf(const char *fmt, ...)
198 {
199 char para_name[GWP_ASAN_NAME_LEN] = "gwp_asan.log.path";
200 static CachedHandle para_handler = NULL;
201 if (para_handler == NULL) {
202 para_handler = CachedParameterCreate(para_name, "default");
203 }
204 const char *para_value = CachedParameterGet(para_handler);
205 if (strcmp(para_value, "file") == 0) {
206 char process_short_name[GWP_ASAN_NAME_LEN];
207 char *path = get_process_short_name(process_short_name, GWP_ASAN_NAME_LEN);
208 if (!path) {
209 MUSL_LOGE("[gwp_asan]: get_process_short_name failed!");
210 return;
211 }
212 char log_path[GWP_ASAN_NAME_LEN];
213 snprintf(log_path, GWP_ASAN_NAME_LEN, "%s%s.%s.%d.log", GWP_ASAN_LOG_DIR, GWP_ASAN_LOG_TAG, path, getpid());
214 FILE *fp = fopen(log_path, "a+");
215 if (!fp) {
216 MUSL_LOGE("[gwp_asan]: %{public}s fopen %{public}s failed!", path, log_path);
217 return;
218 } else {
219 MUSL_LOGE("[gwp_asan]: %{public}s fopen %{public}s succeed.", path, log_path);
220 }
221 va_list ap;
222 va_start(ap, fmt);
223 int result = vfprintf(fp, fmt, ap);
224 va_end(ap);
225 fclose(fp);
226 if (result < 0) {
227 MUSL_LOGE("[gwp_asan] %{public}s write log failed!\n", path);
228 }
229 return;
230 }
231 if (strcmp(para_value, "default") == 0) {
232 va_list ap;
233 va_start(ap, fmt);
234 char log_buffer[PATH_MAX];
235 int result = vsnprintf(log_buffer, PATH_MAX, fmt, ap);
236 va_end(ap);
237 if (result < 0) {
238 MUSL_LOGE("[gwp_asan] write log failed!\n");
239 }
240 if (WriteSanitizerLog != NULL) {
241 WriteSanitizerLog(log_buffer, strlen(log_buffer), "faultlogger");
242 return;
243 }
244 if (try_load_asan_logger) {
245 return;
246 }
247 pthread_mutex_lock(&gwpasan_mutex);
248 if (WriteSanitizerLog != NULL) {
249 WriteSanitizerLog(log_buffer, strlen(log_buffer), "faultlogger");
250 pthread_mutex_unlock(&gwpasan_mutex);
251 return;
252 }
253 if (!try_load_asan_logger && handle == NULL) {
254 handle = dlopen(ASAN_LOG_LIB, RTLD_LAZY);
255 if (handle == NULL) {
256 pthread_mutex_unlock(&gwpasan_mutex);
257 return;
258 }
259 try_load_asan_logger = true;
260 *(void**)(&WriteSanitizerLog) = dlsym(handle, "WriteSanitizerLog");
261 if (WriteSanitizerLog != NULL) {
262 WriteSanitizerLog(log_buffer, strlen(log_buffer), "faultlogger");
263 }
264 }
265 pthread_mutex_unlock(&gwpasan_mutex);
266 return;
267 }
268 if (strcmp(para_value, "stdout") == 0) {
269 va_list ap;
270 va_start(ap, fmt);
271 int result = vfprintf(stdout, fmt, ap);
272 va_end(ap);
273 if (result < 0) {
274 MUSL_LOGE("[gwp_asan] write log failed!\n");
275 }
276 return;
277 }
278 }
279
gwp_asan_printf_backtrace(uintptr_t * trace_buffer,size_t trace_length,printf_t gwp_asan_printf)280 void gwp_asan_printf_backtrace(uintptr_t *trace_buffer, size_t trace_length, printf_t gwp_asan_printf)
281 {
282 if (trace_length == 0) {
283 gwp_asan_printf("It dosen't see any stack trace!\n");
284 }
285 for (size_t i = 0; i < trace_length; i++) {
286 if (trace_buffer[i]) {
287 Dl_info info;
288 if (dladdr(trace_buffer[i], &info)) {
289 size_t offset = trace_buffer[i] - (uintptr_t)info.dli_fbase;
290 gwp_asan_printf(" #%zu %p (%s+%p)\n", i, trace_buffer[i], info.dli_fname, offset);
291 } else {
292 gwp_asan_printf(" #%zu %p\n", i, trace_buffer[i]);
293 }
294 }
295 }
296 gwp_asan_printf("\n");
297 }
298
299 // Strip pc because pc may have been protected by pac(Pointer Authentication) when build with "-mbranch-protection".
strip_pac_pc(size_t ptr)300 size_t strip_pac_pc(size_t ptr)
301 {
302 #if defined(MUSL_AARCH64_ARCH)
303 register size_t x30 __asm__("x30") = ptr;
304 // "xpaclri" is a NOP on pre armv8.3-a arch.
305 __asm__ volatile("xpaclri" : "+r"(x30));
306 return x30;
307 #else
308 return ptr;
309 #endif
310 }
311
312 /* This function is used for gwp_asan to record the call stack when allocate and deallocate.
313 * So we implemented a fast unwind function by using fp.
314 * The unwind process may stop because the value of fp is incorrect(fp was not saved on the stack due to optimization)
315 * We can build library with "-fno-omit-frame-pointer" to get a more accurate call stack.
316 * The basic unwind principle is as follows:
317 * Stack: func1->func2->func3
318 * --------------------| [Low Adress]
319 * | fp | |-------->|
320 * | lr | func3 | |
321 * | ...... | | |
322 * --------------------|<--------|
323 * | fp | |-------->|
324 * | lr | func2 | |
325 * | ...... | | |
326 * --------------------| |
327 * | fp | |<--------|
328 * | lr | func1 |
329 * | ...... | |
330 * --------------------| [High Address]
331 */
run_unwind(size_t * frame_buf,size_t num_frames,size_t max_record_stack,size_t current_frame_addr)332 GWP_ASAN_NO_ADDRESS size_t run_unwind(size_t *frame_buf,
333 size_t num_frames,
334 size_t max_record_stack,
335 size_t current_frame_addr)
336 {
337 size_t stack_end = (size_t)(__pthread_self()->stack);
338 size_t prev_fp = 0;
339 size_t prev_lr = 0;
340 while (true) {
341 unwind_info *frame = (unwind_info*)(current_frame_addr);
342 GWP_ASAN_LOGD("[gwp_asan] unwind info:%{public}d cur:%{public}p, end:%{public}p fp:%{public}p lr:%{public}p \n",
343 num_frames, current_frame_addr, stack_end, frame->fp, frame->lr);
344 size_t stripped_lr = strip_pac_pc(frame->lr);
345 if (!stripped_lr) {
346 break;
347 }
348 if (num_frames < max_record_stack) {
349 frame_buf[num_frames] = stripped_lr - 4;
350 }
351 ++num_frames;
352 if (frame->fp == prev_fp || frame->lr == prev_lr || frame->fp < current_frame_addr + sizeof(unwind_info) ||
353 frame->fp >= stack_end || frame->fp % sizeof(void*) != 0) {
354 break;
355 }
356 prev_fp = frame->fp;
357 prev_lr = frame->lr;
358 current_frame_addr = frame->fp;
359 }
360
361 return num_frames;
362 }
363
libc_gwp_asan_unwind_fast(size_t * frame_buf,size_t max_record_stack)364 GWP_ASAN_NO_ADDRESS size_t libc_gwp_asan_unwind_fast(size_t *frame_buf, size_t max_record_stack)
365 {
366 size_t current_frame_addr = __builtin_frame_address(0);
367 size_t num_frames = 0;
368
369 return run_unwind(frame_buf, num_frames, max_record_stack, current_frame_addr);
370 }
371
372 // Get fault address from sigchain in signal handler
libc_gwp_asan_unwind_segv(size_t * frame_buf,size_t max_record_stack,void * signal_context)373 GWP_ASAN_NO_ADDRESS size_t libc_gwp_asan_unwind_segv(size_t *frame_buf, size_t max_record_stack, void *signal_context)
374 {
375 ucontext_t *context = (ucontext_t *)signal_context;
376 size_t current_frame_addr = get_frame_pointer(context);
377 size_t pc = get_pc(context);
378 size_t num_frames = 0;
379
380 if (current_frame_addr && pc) {
381 frame_buf[num_frames] = strip_pac_pc(pc);
382 ++num_frames;
383 }
384 return run_unwind(frame_buf, num_frames, max_record_stack, current_frame_addr);
385 }
386
may_init_gwp_asan(bool force_init)387 bool may_init_gwp_asan(bool force_init)
388 {
389 GWP_ASAN_LOGD("[gwp_asan]: may_init_gwp_asan enter force_init:%{public}d.\n", force_init);
390 if (gwp_asan_initialized) {
391 GWP_ASAN_LOGD("[gwp_asan]: may_init_gwp_asan return because gwp_asan_initialized is true.\n");
392 return false;
393 }
394 #ifdef OHOS_ENABLE_PARAMETER
395 // Turn off gwp_asan.
396 if (GWP_ASAN_PREDICT_FALSE(is_gwp_asan_disable())) {
397 GWP_ASAN_LOGD("[gwp_asan]: may_init_gwp_asan return because gwp_asan is disable by env.\n");
398 return false;
399 }
400 #endif
401
402 if (!force_init && !should_sample_process()) {
403 GWP_ASAN_LOGD("[gwp_asan]: may_init_gwp_asan return because sample not hit.\n");
404 return false;
405 }
406
407 #ifdef OHOS_ENABLE_PARAMETER
408 // All memory allocations use gwp_asan.
409 force_sample_alloctor_by_env();
410 #endif
411
412 gwp_asan_option gwp_asan_option = {
413 .enable = true,
414 .install_fork_handlers = true,
415 .install_signal_handlers = true,
416 .max_simultaneous_allocations = MAX_SIMULTANEOUS_ALLOCATIONS,
417 .sample_rate = SAMPLE_RATE,
418 .backtrace = libc_gwp_asan_unwind_fast,
419 .gwp_asan_printf = gwp_asan_printf,
420 .printf_backtrace = gwp_asan_printf_backtrace,
421 .segv_backtrace = libc_gwp_asan_unwind_segv,
422 };
423
424 char buf[GWP_ASAN_NAME_LEN];
425 char *path = get_process_short_name(buf, GWP_ASAN_NAME_LEN);
426 if (!path) {
427 return false;
428 }
429
430 MUSL_LOGE("[gwp_asan]: %{public}d %{public}s gwp_asan initializing.\n", getpid(), path);
431 init_gwp_asan((void*)&gwp_asan_option);
432 gwp_asan_initialized = true;
433 MUSL_LOGE("[gwp_asan]: %{public}d %{public}s gwp_asan initialized.\n", getpid(), path);
434 return true;
435 }
init_gwp_asan_by_libc(bool force_init)436 bool init_gwp_asan_by_libc(bool force_init)
437 {
438 char buf[GWP_ASAN_NAME_LEN];
439 char *path = get_process_short_name(buf, GWP_ASAN_NAME_LEN);
440 if (!path) {
441 return false;
442 }
443 // We don't sample appspawn, and the chaild process decides whether to sample or not.
444 if (strcmp(path, "appspawn") == 0 || strcmp(path, "sh") == 0) {
445 return false;
446 }
447 return may_init_gwp_asan(force_init);
448 }
449
get_platform_gwp_asan_tls_slot()450 void* get_platform_gwp_asan_tls_slot()
451 {
452 return (void*)(&(__pthread_self()->gwp_asan_tls));
453 }
454
libc_gwp_asan_malloc(size_t bytes)455 void* libc_gwp_asan_malloc(size_t bytes)
456 {
457 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
458 return MuslFunc(malloc)(bytes);
459 }
460 void *res = NULL;
461 if (GWP_ASAN_PREDICT_FALSE(force_sample_alloctor || gwp_asan_should_sample())) {
462 res = gwp_asan_malloc(bytes);
463 if (res != NULL) {
464 return res;
465 }
466 }
467 return MuslFunc(malloc)(bytes);
468 }
469
libc_gwp_asan_calloc(size_t nmemb,size_t size)470 void* libc_gwp_asan_calloc(size_t nmemb, size_t size)
471 {
472 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
473 return MuslFunc(calloc)(nmemb, size);
474 }
475
476 if (GWP_ASAN_PREDICT_FALSE(force_sample_alloctor || gwp_asan_should_sample())) {
477 size_t total_bytes;
478 void* result = NULL;
479 if (!__builtin_mul_overflow(nmemb, size, &total_bytes)) {
480 GWP_ASAN_LOGD("[gwp_asan]: call gwp_asan_malloc nmemb:%{public}d size:%{public}d.\n", nmemb, size);
481 result = gwp_asan_malloc(total_bytes);
482 if (result != NULL) {
483 return result;
484 }
485 }
486 }
487
488 return MuslFunc(calloc)(nmemb, size);
489 }
490
libc_gwp_asan_realloc(void * ptr,size_t size)491 void* libc_gwp_asan_realloc(void *ptr, size_t size)
492 {
493 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
494 return MuslFunc(realloc)(ptr, size);
495 }
496
497 if (GWP_ASAN_PREDICT_FALSE(gwp_asan_pointer_is_mine(ptr))) {
498 GWP_ASAN_LOGD("[gwp_asan]: call gwp_asan_malloc ptr:%{public}p size:%{public}d.\n", ptr, size);
499 if (GWP_ASAN_PREDICT_FALSE(size == 0)) {
500 gwp_asan_free(ptr);
501 return NULL;
502 }
503
504 void* new_addr = gwp_asan_malloc(size);
505 if (new_addr != NULL) {
506 size_t old_size = gwp_asan_get_size(ptr);
507 memcpy(new_addr, ptr, (size < old_size) ? size : old_size);
508 gwp_asan_free(ptr);
509 return new_addr;
510 } else {
511 // Use the default allocator if gwp malloc failed.
512 void* addr_of_default_allocator = MuslFunc(malloc)(size);
513 if (addr_of_default_allocator != NULL) {
514 size_t old_size = gwp_asan_get_size(ptr);
515 memcpy(addr_of_default_allocator, ptr, (size < old_size) ? size : old_size);
516 gwp_asan_free(ptr);
517 return addr_of_default_allocator;
518 } else {
519 return NULL;
520 }
521 }
522 }
523 return MuslFunc(realloc)(ptr, size);
524 }
525
libc_gwp_asan_free(void * addr)526 void libc_gwp_asan_free(void *addr)
527 {
528 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
529 return MuslFunc(free)(addr);
530 }
531 if (GWP_ASAN_PREDICT_FALSE(gwp_asan_pointer_is_mine(addr))) {
532 return gwp_asan_free(addr);
533 }
534 return MuslFunc(free)(addr);
535 }
536
libc_gwp_asan_malloc_usable_size(void * addr)537 size_t libc_gwp_asan_malloc_usable_size(void *addr)
538 {
539 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
540 return MuslMalloc(malloc_usable_size)(addr);
541 }
542 if (GWP_ASAN_PREDICT_FALSE(gwp_asan_pointer_is_mine(addr))) {
543 return gwp_asan_get_size(addr);
544 }
545 return MuslMalloc(malloc_usable_size)(addr);
546 }
547
libc_gwp_asan_malloc_iterate(void * base,size_t size,void (* callback)(uintptr_t base,size_t size,void * arg),void * arg)548 void libc_gwp_asan_malloc_iterate(void *base, size_t size,
549 void (*callback)(uintptr_t base, size_t size, void *arg), void *arg)
550 {
551 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
552 return;
553 }
554 if (GWP_ASAN_PREDICT_FALSE(gwp_asan_pointer_is_mine(base))) {
555 return gwp_asan_iterate(base, size, callback, arg);
556 }
557 return;
558 }
559
libc_gwp_asan_malloc_disable()560 void libc_gwp_asan_malloc_disable()
561 {
562 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
563 return;
564 }
565
566 return gwp_asan_disable();
567 }
568
libc_gwp_asan_malloc_enable()569 void libc_gwp_asan_malloc_enable()
570 {
571 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
572 return;
573 }
574
575 return gwp_asan_enable();
576 }
577
libc_gwp_asan_has_free_mem()578 bool libc_gwp_asan_has_free_mem()
579 {
580 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
581 return false;
582 }
583 gwp_asan_disable();
584 int res = gwp_asan_has_free_mem();
585 gwp_asan_enable();
586 return res;
587 }
libc_gwp_asan_ptr_is_mine(void * addr)588 bool libc_gwp_asan_ptr_is_mine(void *addr)
589 {
590 if (GWP_ASAN_PREDICT_TRUE(!gwp_asan_initialized)) {
591 return false;
592 }
593
594 return gwp_asan_pointer_is_mine(addr);
595 }
596 #else
597 #include <stdbool.h>
598
599 // Used for appspawn.
may_init_gwp_asan(bool force_init)600 bool may_init_gwp_asan(bool force_init)
601 {
602 return false;
603 }
604 #endif
605