I am dealing with a C program that reads in from a file with many
lines of about 60 characters each and allocates the string in memory
by requesting more memory as it reads the file in. After each malloc
request, it checks with a function OOM() if the request for more
memory was successful.
I have tested the program with an increasingly larger input file, and
OOM() reports an "Out of memory" more or less when the memory
usage reaches 1.2G when looking at the top command while the program
is running. This is on a 64bit linux machine with plenty more memory
available. Output from file /my/binary/program:
ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
My question is: why is it reaching the 1.2G limit? I remember my
sysadmin used to say that certain binaries would only be able to use
up to 1.2G, which coincidentally is what I am seeing here.
When I run qsub the same execution on a node of the same 64bit Linux
SGE grid reserving 50GB of memory, it reports that also goes "Out of
memory" and the following SGE log memory footprint:
Max vmem = 2.313G
Any ideas why the program is reaching this memory limit? Are there any
compilation flags I should be aware of which could cause/solve this?
Find below the relevant flags in the current Makefile:
CC = gcc
CFLAGS = -Wall -O3 -funroll-loops -DNDEBUG -fomit-frame-pointer -std=gnu99 -msse2 -Wno-unused-function -Wno-unused-result
CFLAGSSFMT = -msse2 -DHAVE_SSE2 -O9 -finline-functions -fomit-frame-pointer \
-DNDEBUG -fno-strict-aliasing --param max-inline-insns-single=1800 -std=c99
LD = ld
LDFLAGS = -lm -lc -lblas -llapack
INCFLAGS =
DEFINES = -D_GNU_SOURCE -DUSE_BLAS
Some of the relevant code belo:w
in mystring.h:
#ifndef _MYSTRING_H_
#define _MYSTRING_H_
struct __mystring_struct {
char * string;
int len, maxlen;
};
typedef struct __mystring_struct * Mystring;
#define Mystring_size sizeof(struct __mystring_struct)
Mystring new_mystring (const int len);
void free_mystring (Mystring string);
void append_char_to_mystring ( const char c, Mystring string);
char * cstring_of_mystring(const Mystring string);
Mystring mystring_of_cstring (const char * str);
#endif
in mystring.c:
#include <string.h>
#include "mystring.h"
#define OOM(A) { if (NULL==(A) ){fputs("Out of memory\n",stderr); exit(EXIT_FAILURE);} }
static void check_is_mystring (const Mystring string);
static void double_length_of_mystring ( Mystring string);
Later on:
static void double_length_of_mystring (Mystring string){
char * new_mem;
check_is_mystring(string);
new_mem = malloc(string->maxlen * 2 * sizeof(char)); OOM(new_mem);
memcpy (new_mem,string->string,string->len * sizeof(char));
free(string->string);
string->string = new_mem;
string->maxlen *= 2;
check_is_mystring (string);
}
It seems you use int to keep size of your string. In GCC (and in most other PC platform compilers) this type is 32b even on 64b platform. You should use size_t instead.
The mechanism of the failed allocation is as follows:
1.2 GB ~= 1288490189
2 * 1.2 GB ~= 2576980378, which is over 2^31 (2147483648), after overflow you get -1717986918 in 2nd complement arithmetics
When calling malloc, -1717986918 is sign extended to a 64 b, and then casted to unsigned 64b, which gives you 2^64 - 1717986918, which is only marginally less than 2^64, and definitely more than the memory you have in the system.
Related
I am using a arm cortex-a55 based MPU and use the toolchain gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar from 'developer.arm.com'. I just want to build a simple bare-metal application and will use freertos multi-task environment later.
In the Makefile:
set CC=/opt/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin/aarch64-none-elf-gcc
c source compile options:
$CC -mcpu=cortex-a55 -Wall -fomit-frame-pointer -fno-stack-protector -ffunction-sections -fdata-sections
linking options:
$CC -Wl,--gc-sections -nostartfiles -T,link.lds -Wl,-Map,System.map
I write my own startup.S with _start entry point. It simply does data section relocation and bss clear, sp setup and bl to main().
int main()
{
stdout = fopen("uart", "w");
printf("Hello, world!\n");
while(1);
}
I also implement the platform depandant fuctions:
int _open(const char *pname, int flags){
uart_init();
return 1;
}
ssize_t _write(int fd, const void *buf, size_t count){
char *str = (char *)buf;
uart_print(str);
return (ssize_t)count;
}
void * _sbrk(ptrdiff_t incr)
{
static char *heap = NULL;
char *prev_heap;
if(NULL == heap)
heap = (char *)asm_get_heap_start();
prev_heap = heap;
heap += incr;
/* total 1KB heap area */
if((u64)heap > asm_get_heap_end())
{
errno = -ENOMEM;
return NULL;
}
return (void *)prev_heap;
}
and others like _read(), _lseek()...
After compile and link, download the raw binary to board and run. Nothing output. After debug I find the cpu hangs inside fopen(). If I replace fopen() with uart_init(), it will hang inside printf().
I am sure the uart_init() and uart_print() are OK. Because if I don't use fopen() and printf(), and use uart_init() and uart_print() directly, it works fine.
I am not sure if I miss some macro(s) needed for .c source file. There are lots of macros in the newlib/libc/include header files. Or something need to be done before 'printf()'.
Please advise, thanks.
This is a 40-line MCVE (Minimal, Complete, Verifiable Example) — or something close to minimal — cut down from a 1675 line source file that originally included 32 headers (and most of those included multiple other headers — compiling it with gcc -H lists 464 headers from the project and the system, many of them several times). That file is working code that previously compiled without warnings (GCC 8.3.0), but not with GCC 9.1.0. All structure, function, type, variable names have been changed.
pf31.c
#include <string.h>
enum { SERVERNAME_LEN = 128 };
typedef struct ServerQueue
{
char server_name[SERVERNAME_LEN + 1];
struct ServerQueue *next;
} ServerQueue;
extern int function_under_test(char *servername);
#ifdef SUPPRESS_BUG
extern int function_using_name(char *name);
#endif /* SUPPRESS_BUG */
extern int GetServerQueue(const char *servername, ServerQueue *queue);
int
function_under_test(char *servername)
{
ServerQueue queue;
char name[SERVERNAME_LEN + 1];
if (GetServerQueue(servername, &queue) != 0)
return -1;
char *name_in_queue = queue.server_name;
if (name_in_queue)
strncpy(name, name_in_queue, SERVERNAME_LEN);
else
strncpy(name, servername, SERVERNAME_LEN);
name[SERVERNAME_LEN] = '\0';
#ifdef SUPPRESS_BUG
return function_using_name(name);
#else
return 0;
#endif /* SUPPRESS_BUG */
}
Compilation
When compiled using GCC 9.1.0 (on a Mac running macOS 10.14.5 Mojave, or on a Linux VM running RedHat 5.x — don't ask!), with the option -DSUPPRESS_BUG I get no error, but with the option -USUPPRESS_BUG, I get an error:
$ gcc -std=c11 -O3 -g -Wall -Wextra -Werror -DSUPPRESS_BUG -c pf31.c
$ gcc -std=c11 -O3 -g -Wall -Wextra -Werror -USUPPRESS_BUG -c pf31.c
In file included from /usr/include/string.h:417,
from pf31.c:1:
pf31.c: In function ‘function_under_test’:
pf31.c:30:9: error: ‘__builtin_strncpy’ output may be truncated copying 128 bytes from a string of length 128 [-Werror=stringop-truncation]
30 | strncpy(name, name_in_queue, SERVERNAME_LEN);
| ^~~~~~~
cc1: all warnings being treated as errors
$
When I compile using GCC 8.3.0, I get no errors reported.
Question
Two sides of one question:
Why does GCC 9.1.0 complain about the use of strncpy() when the code is compiled with -USUPPRESS_BUG?
Why doesn't it complain when the code is compiled with -DSUPPRESS_BUG?
Corollary: is there a way to work around this unwanted warning that works with older GCC versions as well as 9.1.0. I've not yet found one. There's also a strong element of "I don't think it should be necessary, because this is using strncpy() to limit the amount of data copied, which is what it is designed for".
Another variant
I have another non-erroring variant, changing the signature of the function_under_test() — here's a set of diffs:
11c11
< extern int function_under_test(char *servername);
---
> extern int function_under_test(char *servername, ServerQueue *queue);
20c20
< function_under_test(char *servername)
---
> function_under_test(char *servername, ServerQueue *queue)
22d21
< ServerQueue queue;
25c24
< if (GetServerQueue(servername, &queue) != 0)
---
> if (GetServerQueue(servername, queue) != 0)
27c26
< char *name_in_queue = queue.server_name;
---
> char *name_in_queue = queue->server_name;
This compiles cleanly regardless of whether SUPPRESS_BUG is defined or not.
As you can guess from the SUPPRESS_BUG terminology, I'm tending towards the view that this is bug in GCC, but I'm kinda cautious about claiming it is one just yet.
More about the the original code: the function itself was 540 lines long; the strncpy() block occurs about 170 lines into the function; the variable corresponding to name was used further down the function in a number of function call, some of which take name as an argument and supply a return value for the function. This corresponds more to the -DSUPPRESS_BUG code, except that in the 'real code', the bug is not suppressed.
This is a GCC bug tracked as PR88780. According to Martin's comment, this warning did not exist prior to GCC 8.
GCC is shipped with this known bug, as it is not deemed release-critical.
To be honest, I am not 100% sure it is the bug. The point is, there are known false-positives. If you feel like helping the GCC project, you can find the most appropriate bug among strncpy / Wstringop-truncation bugs and post your example there. It would be more helpful if you minimized it further (say, with creduce); minimizing the compile string is also appreciated (that would be rather trivial, I guess).
Several compilation warnings related to strncpy were found in GCC 9.0 and reported here and here.
One of them is the error mentioned in the question which seems to occur in the file string_fortified.h:
/usr/include/bits/string_fortified.h:106:10: warning: ‘__builtin_strncpy’ output may be truncated copying 16 bytes from a string of length 16 [-Wstringop-truncation]
106 | return __builtin___strncpy_chk (__dest, __src, __len, __bos (__dest));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The response to this was given on April 15, 2019 was:
Thank you for the report, however as GCC 9 still under development. We do not see the above errors in the current stable GCC 7.4 or GCC 8.3. We appreciate the advanced notice, and will accept PRs to fix issues against GCC 9, but for now our target compiler is gcc stable.
So I believe the errors are probably a result of versions 9 and 9.1 being not stable versions. Hopefully they will be eliminated when these versions become stable.
In the book I am reading, Software Exorcism, has this example code for a buffer overflow:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 4
void victim(char *str)
{
char buffer[BUFFER_SIZE];
strcpy(buffer,str);
return;
}
void redirected()
{
printf("\tYou've been redirected!\n");
exit(0);
return;
}
void main()
{
char buffer[]=
{
'1','2','3','4',
'5','6','7','8',
'\x0','\x0','\x0','\x0','\x0'
};
void *fptr;
unsigned long *lptr;
printf("buffer = %s\n", buffer);
fptr = redirected;
lptr = (unsigned long*)(&buffer[8]);
*lptr = (unsigned long)fptr;
printf("main()\n");
victim(buffer);
printf("main()\n");
return;
}
I can get this to work in Windows with Visual Studio 2010 by specifying
Basic Runtime Checks -> Uninitialized variables
Buffer Security Check -> No
With those compile options, I get this behavior when running:
buffer = 12345678
main()
You've been redirected!
My question is about the code not working on Linux. Is there any clear reason why it is so?
Some info on what I've tried:
I've tried to run this with 32-bit Ubuntu 12.04 (downloaded from here), with these options:
[09/01/2014 11:46] root#ubuntu:/home/seed# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
Getting:
[09/01/2014 12:03] seed#ubuntu:~$ gcc -fno-stack-protector -z execstack -o overflow overflow.c
[09/01/2014 12:03] seed#ubuntu:~$ ./overflow
buffer = 12345678
main()
main()
Segmentation fault (core dumped)
And with 64-bit CentOS 6.0, with these options:
[root]# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
[root]# sysctl -w kernel.exec-shield=0
kernel.exec-shield = 0
Getting:
[root]# gcc -fno-stack-protector -z execstack -o overflow overflow.c
[root]# ./overflow
buffer = 12345678
main()
main()
[root]#
Is there something fundamentally different in Linux environment, which would cause the example not working, or am I missing something simple here?
Note: I've been through the related questions such as this one and this one, but haven't been able to find anything that would help on this. I don't think this is a duplicate of previous questions even though there are a lot of them.
Your example overflows the stack, a small and predictable memory layout, in an attempt to modify the return address of the function void victim(), which would then point to void redirected() instead of coming back to main().
It works with Visual. But GCC is a different compiler, and can use some different stack allocation rule, making the exploit fail. C doesn't enforce a strict "stack memory layout", so compilers can make different choices.
A good way to view this hypothesis is to test your code using MinGW (aka GCC for Windows), proving the behavior difference is not related strictly to the OS.
#define BUFFER_SIZE 4
void victim(char *str)
{
char buffer[BUFFER_SIZE];
strcpy(buffer,str);
return;
}
There is another potential problem here if optimizations are enabled. buffer is 12 bytes, and its called as victim(buffer). Then, within victim, you try to copy 12 bytes into a 4 byte buffer with strcpy.
FORTIFY_SOURCES should cause the program to seg fault on the call to strcpy. If the compiler can deduce the destination buffer size (which it should in this case), then the compiler will replace strcpy with a "safer" version that includes the destination buffer size. If the bytes to copy exceeds the destination buffer size, then the "safer" strcpy will call abort().
To turn off FORTIFY_SOURCES, then compile with -U_FORTIFY_SOURCE or -D_FORTIFY_SOURCE=0.
I am trying to make the .text section writable for a C program. I looked through the options provided in this SO question and zeroed on modifying the linker script to achieve this.
For this I created a writable memory region using
MEMORY { rwx (wx) : ORIGIN = 0x400000, LENGTH = 256K}
and at the section .text added:
.text :
{
*(.text.unlikely .text.*_unlikely)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(.text .stub .text.* .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
} >rwx
On compiling the code with gcc flag -T and giving my linker file as an argument I am getting an error:
error: no memory region specified for loadable section '.interp'
I am only trying to change the memory permissions for the .text region. Working on Ubuntu x86_64 architecture.
Is there a better way to do this?
Any help is highly appreciated.
Thanks
The Linker Script
Linker Script on pastie.org
In Linux, you can use mprotect() to enable/disable text section write protection from the runtime code; see the Notes section in man 2 mprotect.
Here is a real-world example. First, however, a caveat:
I consider this just a proof of concept implementation, and not something I'd ever use in a real world application. It may look enticing for use in a high-performance library of some sort, but in my experience, changing the API (or the paradigm/approach) of the library usually yields much better results -- and fewer hard-to-debug bugs.
Consider the following six files:
foo1.c:
int foo1(const int a, const int b) { return a*a - 2*a*b + b*b; }
foo2.c:
int foo2(const int a, const int b) { return a*a + b*b; }
foo.h.header:
#ifndef FOO_H
#define FOO_H
extern int foo1(const int a, const int b);
extern int foo2(const int a, const int b);
foo.h.footer:
#endif /* FOO_H */
main.c:
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include "foo.h"
int text_copy(const void *const target,
const void *const source,
const size_t length)
{
const long page = sysconf(_SC_PAGESIZE);
void *start = (char *)target - ((long)target % page);
size_t bytes = length + (size_t)((long)target % page);
/* Verify sane page size. */
if (page < 1L)
return errno = ENOTSUP;
/* Although length should not need to be a multiple of page size,
* adjust it up if need be. */
if (bytes % (size_t)page)
bytes = bytes + (size_t)page - (bytes % (size_t)page);
/* Disable write protect on target pages. */
if (mprotect(start, bytes, PROT_READ | PROT_WRITE | PROT_EXEC))
return errno;
/* Copy code.
* Note: if the target code is being executed, we're in trouble;
* this offers no atomicity guarantees, so other threads may
* end up executing some combination of old/new code.
*/
memcpy((void *)target, (const void *)source, length);
/* Re-enable write protect on target pages. */
if (mprotect(start, bytes, PROT_READ | PROT_EXEC))
return errno;
/* Success. */
return 0;
}
int main(void)
{
printf("foo1(): %d bytes at %p\n", foo1_SIZE, foo1_ADDR);
printf("foo2(): %d bytes at %p\n", foo2_SIZE, foo2_ADDR);
printf("foo1(3, 5): %d\n", foo1(3, 5));
printf("foo2(3, 5): %d\n", foo2(3, 5));
if (foo2_SIZE < foo1_SIZE) {
printf("Replacing foo1() with foo2(): ");
if (text_copy(foo1_ADDR, foo2_ADDR, foo2_SIZE)) {
printf("%s.\n", strerror(errno));
return 1;
}
printf("Done.\n");
} else {
printf("Replacing foo2() with foo1(): ");
if (text_copy(foo2_ADDR, foo1_ADDR, foo1_SIZE)) {
printf("%s.\n", strerror(errno));
return 1;
}
printf("Done.\n");
}
printf("foo1(3, 5): %d\n", foo1(3, 5));
printf("foo2(3, 5): %d\n", foo2(3, 5));
return 0;
}
function-info.bash:
#!/bin/bash
addr_prefix=""
addr_suffix="_ADDR"
size_prefix=""
size_suffix="_SIZE"
export LANG=C
export LC_ALL=C
nm -S "$#" | while read addr size kind name dummy ; do
[ -n "$addr" ] || continue
[ -n "$size" ] || continue
[ -z "$dummy" ] || continue
[ "$kind" = "T" ] || continue
[ "$name" != "${name#[A-Za-z]}" ] || continue
printf '#define %s ((void *)0x%sL)\n' "$addr_prefix$name$addr_suffix" "$addr"
printf '#define %s %d\n' "$size_prefix$name$size_suffix" "0x$size"
done || exit $?
Remember to make it executable using chmod u+x ./function-info.bash
First, compile the sources using valid sizes but invalid addresses:
gcc -W -Wall -O3 -c foo1.c
gcc -W -Wall -O3 -c foo2.c
( cat foo.h.header ; ./function-info.bash foo1.o foo2.o ; cat foo.h.footer) > foo.h
gcc -W -Wall -O3 -c main.c
The sizes are correct but the addresses are not, because the code is yet to be linked. Relative to the final binary, the object file contents are usually relocated at link time. So, link the sources to get example executable, example:
gcc -W -Wall -O3 main.o foo1.o foo2.o -o example
Extract the correct (sizes and) addresses:
( cat foo.h.header ; ./function-info.bash example ; cat foo.h.footer) > foo.h
Recompile and link,
gcc -W -Wall -O3 -c main.c
gcc -W -Wall -O3 foo1.o foo2.o main.o -o example
and verify that the constants now do match:
mv -f foo.h foo.h.used
( cat foo.h.header ; ./function-info.bash example ; cat foo.h.footer) > foo.h
cmp -s foo.h foo.h.used && echo "Done." || echo "Recompile and relink."
Due to high optimization (-O3) the code that utilizes the constants may change size, requiring a yet another recompile-relink. If the last line outputs "Recompile and relink", just repeat the last two steps, i.e. five lines.
(Note that since foo1.c and foo2.c do not use the constants in foo.h, they obviously do not need to be recompiled.)
On x86_64 (GCC-4.6.3-1ubuntu5), running ./example outputs
foo1(): 21 bytes at 0x400820
foo2(): 10 bytes at 0x400840
foo1(3, 5): 4
foo2(3, 5): 34
Replacing foo1() with foo2(): Done.
foo1(3, 5): 34
foo2(3, 5): 34
which shows that the foo1() function indeed was replaced. Note that the longer function is always replaced with the shorter one, because we must not overwrite any code outside the two functions.
You can modify the two functions to verify this; just remember to repeat the entire procedure (so that you use the correct _SIZE and _ADDR constants in main()).
Just for giggles, here is the generated foo.h for the above:
#ifndef FOO_H
#define FOO_H
extern int foo1(const int a, const int b);
extern int foo2(const int a, const int b);
#define foo1_ADDR ((void *)0x0000000000400820L)
#define foo1_SIZE 21
#define foo2_ADDR ((void *)0x0000000000400840L)
#define foo2_SIZE 10
#define main_ADDR ((void *)0x0000000000400610L)
#define main_SIZE 291
#define text_copy_ADDR ((void *)0x0000000000400850L)
#define text_copy_SIZE 226
#endif /* FOO_H */
You might wish to use a smarter scriptlet, say an awk one that uses nm -S to obtain all function names, addresses, and sizes, and in the header file replaces only the values of existing definitions, to generate your header file. I'd use a Makefile and some helper scripts.
Further notes:
The function code is copied as-is, no relocation etc. is done. (This means that if the machine code of the replacement function contains absolute jumps, the execution continues in the original code. These example functions were chosen, because they're unlikely to have absolute jumps in them. Run objdump -d foo1.o foo2.o to verify from the assembly.)
That is irrelevant if you use the example just to investigate how to modify executable code within the running process. However, if you build runtime-function-replacing schemes on top of this example, you may need to use position independent code for the replaced code (see the GCC manual for relevant options for your architecture) or do your own relocation.
If another thread or signal handler executes the code being modified, you're in serious trouble. You get undefined results. Unfortunately, some libraries start extra threads, which may not block all possible signals, so be extra careful when modifying code that might be run by a signal handler.
Do not assume the compiler compiles the code in a specific way or uses a specific organization. My example uses separate compilation units, to avoid the cases where the compiler might share code between similar functions.
Also, it examines the final executable binary directly, to obtain the sizes and addresses to be modified to modify an entire function implementation. All verifications should be done on the object files or final executable, and disassembly, instead of just looking at the C code.
Putting any code that relies on the address and size constants into a separate compilation unit makes it easier and faster to recompile and relink the binary. (You only need to recompile the code that uses the constants directly, and you can even use less optimization for that code, to eliminate extra recompile-relink cycles, without impacting the overall code quality.)
In my main.c, both the address and length supplied to mprotect() are page-aligned (based on the user parameters). The documents say only the address has to be. Since protections are page-granular, making sure the length is a multiple of the page size does not hurt.
You can read and parse /proc/self/maps (which is a kernel-generated pseudofile; see man 5 proc, /proc/[pid]/maps section, for further info) to obtain the existing mappings and their protections for the current process.
In any case, if you have any questions, I'd be happy to try and clarify the above.
Addendum:
It turns out that using the GNU extension dl_iterate_phdr() you can enable/disable write protection on all text sections trivially:
#define _GNU_SOURCE
#include <unistd.h>
#include <dlfcn.h>
#include <sys/mman.h>
#include <link.h>
static int do_write_protect_text(struct dl_phdr_info *info, size_t size, void *data)
{
const int protect = (data) ? PROT_READ | PROT_EXEC : PROT_READ | PROT_WRITE | PROT_EXEC;
size_t page;
size_t i;
page = sysconf(_SC_PAGESIZE);
if (size < sizeof (struct dl_phdr_info))
return ENOTSUP;
/* Ignore libraries. */
if (info->dlpi_name && info->dlpi_name[0] != '\0')
return 0;
/* Loop over each header. */
for (i = 0; i < (size_t)info->dlpi_phnum; i++)
if ((info->dlpi_phdr[i].p_flags & PF_X)) {
size_t ptr = (size_t)info->dlpi_phdr[i].p_vaddr;
size_t len = (size_t)info->dlpi_phdr[i].p_memsz;
/* Start at the beginning of the relevant page, */
if (ptr % page) {
len += ptr % page;
ptr -= ptr % page;
}
/* and use full pages. */
if (len % page)
len += page - (len % page);
/* Change protections. Ignore unmapped sections. */
if (mprotect((void *)ptr, len, protect))
if (errno != ENOMEM)
return errno;
}
return 0;
}
int write_protect_text(int protect)
{
int result;
result = dl_iterate_phdr(do_write_protect_text, (void *)(long)protect);
if (result)
errno = result;
return result;
}
Here is an example program you can use to test the above write_protect_text() function:
#define _POSIX_C_SOURCE 200809L
int dump_smaps(void)
{
FILE *in;
char *line = NULL;
size_t size = 0;
in = fopen("/proc/self/smaps", "r");
if (!in)
return errno;
while (getline(&line, &size, in) > (ssize_t)0)
if ((line[0] >= '0' && line[0] <= '9') ||
(line[0] >= 'a' && line[0] <= 'f'))
fputs(line, stdout);
free(line);
if (!feof(in) || ferror(in)) {
fclose(in);
return errno = EIO;
}
if (fclose(in))
return errno = EIO;
return 0;
}
int main(void)
{
printf("Initial mappings:\n");
dump_smaps();
if (write_protect_text(0)) {
fprintf(stderr, "Cannot disable write protection on text sections: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
printf("\nMappings with write protect disabled:\n");
dump_smaps();
if (write_protect_text(1)) {
fprintf(stderr, "Cannot enable write protection on text sections: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
printf("\nMappings with write protect enabled:\n");
dump_smaps();
return EXIT_SUCCESS;
}
The example program dumps /proc/self/smaps before and after changing the text section write protection, showing that it indeed does enable/disable write protectio on all text sections (program code). It does not try to alter write protect on dynamically-loaded libraries. This was tested to work on x86-64 using Ubuntu 3.8.0-35-generic kernel.
If you just want to have one executable with a writable .text, you can just link with -N
At least for me, binutils 2.22 , ld -N objectfile.o
will produce a binary that i can happily write around in.
Reading gcc pages, you can pass the linker option from gcc by : gcc -XN source
#include <stdio.h>
#include <string.h>
int main()
{
char arrDst[5] = {0};
char arrSrc[10] = "123456";
memcpy( arrDst, arrSrc, sizeof( arrSrc ) );
return 0;
}
Here in this program it is clear that there is a memory corruption.
Is there any option in gcc compiler by which I can recognize this problem at compile time?
Note: I used valgrind --leak-check=full, but it doesn't help.
$ gcc -Wall -O1 t.c
In file included from /usr/include/string.h:642:0,
from t.c:3:
In function ‘memcpy’,
inlined from ‘main’ at t.c:13:9:
/usr/include/bits/string3.h:52:3: warning: call to __builtin___memcpy_chk
will always overflow destination buffer [enabled by default]
GCC can recognize some of these. That generally requires turning on optimizations (at least -01) and warnings (-Wall, add -Wextra too).
It may not scale to the large program you are really interested in, but you can find this error with Frama-C:
$ frama-c -cpp-command "gcc -C -E -I`frama-c -print-share-path`/libc/ -nostdinc" mem.c `frama-c -print-share-path`/libc/fc_runtime.c -val
...
[value] computing for function memcpy <- main.
Called from mem.c:13.
.../libc/string.h:54:[value] Function memcpy: precondition got status invalid.
This message means that you are calling memcpy() with arguments that do not satisfy its contract. In this case the pre-condition that fails is the first in the list, about the validity of the destination for writing:
/*# requires \valid(((char*)dest)+(0..n - 1));
# requires \valid_read(((char*)src)+(0..n - 1));
# requires \separated(((char *)dest)+(0..n-1),((char *)src)+(0..n-1));
# assigns ((char*)dest)[0..n - 1] \from ((char*)src)[0..n-1];
# assigns \result \from dest;
# ensures memcmp((char*)dest,(char*)src,n) == 0;
# ensures \result == dest;
#*/
extern void *memcpy(void *restrict dest,
const void *restrict src, size_t n);