| /* |
| * Copyright 2012-2014 Google Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #ifndef _LARGEFILE64_SOURCE |
| #define _LARGEFILE64_SOURCE |
| #endif |
| #define __STDC_FORMAT_MACROS |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/wait.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #ifdef mips |
| #include <sys/cachectl.h> |
| #define CACHEFLUSH(p, l, f) cacheflush(p, l, f) |
| #else |
| #define CACHEFLUSH(p, l, f) |
| #endif |
| |
| #define BYTES_PER_LINE 32 |
| #define HONEYPOTPAGES 256 |
| #define LINESIZ 64 |
| #define PFN_BITS 55 |
| |
| uint8_t *honeypot = NULL; |
| size_t honeypotsize = 0; |
| long pagesize = 0; |
| int pagemap_fd = -1; |
| int kpagecount_fd = -1; |
| int kpageflags_fd = -1; |
| |
| void initialize_memory(uint8_t *honeypot, unsigned int seed) { |
| unsigned int i; |
| srand(seed); |
| for (i = 0; i < honeypotsize; ++i) { |
| honeypot[i] = (rand() & 0xff); |
| } |
| } |
| |
| void log_page(uint8_t *mem, int len) { |
| int i; |
| |
| for (i = 0; i < len; ++i) { |
| printf("%02x ", mem[i]); |
| if ((i % BYTES_PER_LINE) == (BYTES_PER_LINE - 1)) printf("\n"); |
| } |
| if ((len % BYTES_PER_LINE) != (BYTES_PER_LINE - 1)) printf("\n"); |
| } |
| |
| off64_t get_proc_offset(void *addr) { |
| unsigned long long vpfn = (unsigned long)addr / pagesize; |
| off64_t off = vpfn * sizeof(unsigned long long); |
| |
| return off; |
| } |
| |
| uint64_t get_pagemap(void *addr) { |
| off64_t roff, off = get_proc_offset(addr); |
| ssize_t rlen; |
| uint64_t pagemap; |
| |
| roff = lseek64(pagemap_fd, off, SEEK_SET); |
| assert(roff == off); |
| rlen = read(pagemap_fd, &pagemap, sizeof(pagemap)); |
| assert(rlen == sizeof(pagemap)); |
| return pagemap; |
| } |
| |
| uint64_t get_kpagecount(uint64_t pfn) { |
| off64_t roff, off = pfn * sizeof(uint64_t); |
| ssize_t rlen; |
| uint64_t kpagecount; |
| |
| roff = lseek64(kpagecount_fd, off, SEEK_SET); |
| assert(roff == off); |
| rlen = read(kpagecount_fd, &kpagecount, sizeof(kpagecount)); |
| assert(rlen == sizeof(kpagecount)); |
| return kpagecount; |
| } |
| |
| uint64_t get_kpageflags(uint64_t pfn) { |
| off64_t roff, off = pfn * sizeof(uint64_t); |
| ssize_t rlen; |
| uint64_t kpageflags; |
| |
| roff = lseek64(kpageflags_fd, off, SEEK_SET); |
| assert(roff == off); |
| rlen = read(kpageflags_fd, &kpageflags, sizeof(kpageflags)); |
| assert(rlen == sizeof(kpageflags)); |
| return kpageflags; |
| } |
| |
| void log_page_difference(uint8_t *honeypot, uint8_t *expected, |
| int len, unsigned int seed, int is_child) { |
| uint64_t pagemap; |
| uint64_t pfn; |
| if (!is_child) { |
| printf("Unexpected memory difference detected in parent, len=%d, " |
| "seed=0x%08x\n", len, seed); |
| } else { |
| printf("Unexpected memory difference detected in child, len=%d, " |
| "seed=0x%08x\n", len, seed); |
| } |
| pagemap = get_pagemap(expected); |
| pfn = pagemap & ((1ULL << PFN_BITS) - 1); |
| printf("Expected: %p pm=0x%" PRIx64 " kc=0x%" PRIx64 " kf=0x%" PRIx64 "\n", |
| expected, pagemap, get_kpagecount(pfn), get_kpageflags(pfn)); |
| log_page(expected, len); |
| |
| pagemap = get_pagemap(honeypot); |
| pfn = pagemap & ((1ULL << PFN_BITS) - 1); |
| printf("Actual: %p pm=0x%" PRIx64 " kc=0x%" PRIx64 " kf=0x%" PRIx64 "\n", |
| honeypot, pagemap, get_kpagecount(pfn), get_kpageflags(pfn)); |
| log_page(honeypot, len); |
| fflush(stdout); |
| } |
| |
| void check_memory(uint8_t *honeypot, unsigned int seed, int is_child) { |
| uint8_t *expected = malloc(honeypotsize); |
| unsigned int i; |
| long j; |
| initialize_memory(expected, seed); |
| for (i = 0; i < honeypotsize; i += pagesize) { |
| int start = -1, end = -1; |
| for (j = 0; j < pagesize; ++j) { |
| if (honeypot[i+j] != expected[i+j]) { |
| if (start < 0) start = i+j; |
| end = i+j; |
| } |
| } |
| if (start != -1) { |
| int len = end - start + 1; |
| log_page_difference(honeypot + start, expected + start, |
| len, seed, is_child); |
| // flush cache and log it again. |
| CACHEFLUSH(honeypot + start, len, DCACHE); |
| CACHEFLUSH(expected + start, len, DCACHE); |
| log_page_difference(honeypot + start, expected + start, |
| len, seed, is_child); |
| // And finally regenerate the expected and log it again. |
| initialize_memory(expected, seed); |
| log_page_difference(honeypot + start, expected + start, |
| len, seed, is_child); |
| } |
| } |
| free(expected); |
| } |
| |
| void corrupt_memory(uint8_t *honeypot) { |
| if ((rand() % 8) == 0) { |
| int offset, len, i; |
| offset = rand() % honeypotsize; |
| len = rand() % 128; |
| printf("Test mode corrupting bytes off=%d, len=%d\n", offset, len); |
| for (i = 0; i < len; ++i) { |
| honeypot[offset + i] ^= rand(); |
| } |
| } |
| } |
| |
| void usage(char *progname) { |
| printf("usage: %s [-t] [-m #pages] [-s sleeptime]\n", progname); |
| printf("\t-t\ttest mode, deliberately introduce random corruption.\n"); |
| printf("\t-m\tmemory to monitor, in megabytes\n"); |
| printf("\t-s\tnumber of seconds to sleep before checking for corruption\n"); |
| exit(1); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| size_t honeypotpages = HONEYPOTPAGES; |
| int testmode = 0; |
| int sleeptime = -1; |
| int rc, c; |
| |
| pagesize = sysconf(_SC_PAGESIZE); |
| assert(pagesize > 0); |
| pagemap_fd = open("/proc/self/pagemap", O_RDONLY); |
| assert(pagemap_fd >= 0); |
| kpagecount_fd = open("/proc/kpagecount", O_RDONLY); |
| assert(kpagecount_fd >= 0); |
| kpageflags_fd = open("/proc/kpageflags", O_RDONLY); |
| assert(kpageflags_fd >= 0); |
| |
| while ((c = getopt(argc, argv, "tm:s:")) != -1) { |
| switch(c) { |
| case 't': testmode = 1; break; |
| case 'm': { |
| ssize_t mbytes = atoi(optarg) * 1024 * 1024; |
| ssize_t pages = mbytes / pagesize; |
| honeypotpages = (pages > 0) ? pages : 1; // -m 0 == minimum memory |
| break; |
| } |
| case 's': sleeptime = atoi(optarg); break; |
| default: usage(argv[0]); break; |
| } |
| } |
| |
| if (sleeptime < 0) { |
| sleeptime = testmode ? 2 : 600; |
| } |
| |
| honeypotsize = honeypotpages * pagesize; |
| printf ("Monitoring %zu bytes every %d seconds\n", honeypotsize, sleeptime); |
| rc = posix_memalign((void **)&honeypot, pagesize, honeypotsize); |
| assert(rc == 0); |
| |
| // Initialize to 0 to force on demand paging for the honeypot. |
| // If the honeypot is not mapped into memory, then no copy on write |
| // will happen for the first fork. |
| memset(honeypot, 0, honeypotsize); |
| |
| while (1) { |
| // Reinitialize on each loop. We only want to log corruption once. |
| unsigned int seed; |
| |
| pid_t child_pid = fork(); |
| int is_child = child_pid == 0; |
| if (child_pid == -1) { |
| perror("Error forking"); |
| } else if (child_pid == 0) { |
| close(pagemap_fd); |
| // close the pagemap fd inherited from the parent |
| pagemap_fd = open("/proc/self/pagemap", O_RDONLY); |
| assert(pagemap_fd >= 0); |
| } |
| |
| seed = time(NULL) + child_pid; |
| initialize_memory(honeypot, seed); |
| CACHEFLUSH(honeypot, honeypotsize, DCACHE); |
| check_memory(honeypot, seed, is_child); |
| |
| sleep(sleeptime); |
| if (testmode) |
| corrupt_memory(honeypot); |
| check_memory(honeypot, seed, is_child); |
| if (child_pid == 0) { |
| exit(0); |
| } |
| |
| wait(NULL); |
| } |
| } |