Use GNU versions of basename() and dirname() in C source - c

How do I use the GNU C Library version of basename() and dirname()?.
If you
#include <libgen.h>
for dirname
You're already getting the POSIX, not the GNU, version of basename(). (Even if you
#define _GNU_SOURCE
As far as I know there is no conditional importing in C. Is there a gcc specific trick?

Just write it yourself and give it a different name than basename. This GNU insistence on creating alternate non-conforming versions of standard functions that can be written in 1-3 lines is completely batty.
char *gnu_basename(char *path)
{
char *base = strrchr(path, '/');
return base ? base+1 : path;
}
This way, your program will also be more portable.

According to the man page you should do
#define _GNU_SOURCE
#include <string.h>
If you get the POSIX version, libgen.h is probably already included before that point. You may want to include -D_GNU_SOURCE in the CPPFLAGS for compilation:
gcc -D_GNU_SOURCE ....
Compare: POSIX Version vs GNU Version on Compiler Explorer.

After examining libgen.h, I'm pretty sure I have a warning-free and error-free solution:
/* my C program */
#define _GNU_SOURCE /* for GNU version of basename(3) */
#include <libgen.h> /* for dirname(3) */
#undef basename /* (snide comment about libgen.h removed) */
#include <string.h> /* for basename(3) (GNU version) and strcmp(3) */
/* rest of C program... */
With the #undef line, now my program includes dirname(3) from libgen.h and the GNU version of basename(3) from string.h.
No compiler warnings/errors from either gcc (version 4.5.2) or clang (version 3.3).

Make sure you're building with the GNU C library, rather than your system's (presumed) POSIX-compatible default.
This is often set in the GCC spec file. Use the -v option to show the current settings:
$ gcc -v
Using built-in specs.
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.4.4-14ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)

It's crazy basename and dirname have two versions.
We worked at a big project, it looks like these two apis already caused
potentially bugs. So we marked "basename" "dirname" as deprecated for warning if
someone use it:
#ifdef basename
__attribute__ ((deprecated))
char *__xpg_basename(char *path);
#else
__attribute__ ((deprecated))
char *basename(const char *path);
#endif
__attribute__ ((deprecated))
char *dirname(char *path);
We also try to introduce a base c foundation library such as glib or libcork,
but it looks like too heavy. So we write a tiny library for this purpose, it
implementation like this:
#include <libgen.h> // for dirname
#include <linux/limits.h> // for PATH_MAX
#include <stdio.h> // for snprintf
#include <string.h> // for basename
#include <stdbool.h> // for bool
bool get_basename(const char *path, char *name, size_t name_size) {
char path_copy[PATH_MAX] = {'\0'};
strncpy(path_copy, path, sizeof(path_copy) - 1);
return snprintf(name, name_size, "%s", basename(path_copy)) < name_size;
}
bool get_dirname(const char *path, char *name, size_t name_size) {
char path_copy[PATH_MAX] = {'\0'};
strncpy(path_copy, path, sizeof(path_copy) - 1);
return snprintf(name, name_size, "%s", dirname(path_copy)) < name_size;
}
Then we replace all basename dirname call with get_basename get_dirname.

Related

No output from split up source, but no warnings either, when omitting an included file

I ran into an issue invoking gcc where if I omit a library .c file, I got no output from the binary (unexpected behavior change) but since this is a missing dependency, I kind of expected the compile to fail (or at least warn)...
Example for this issue is from Head First C page 185 (but is not errata, see my compile mis-step below):
encrypt.h:
void encrypt(char *message);
encrypt.c:
#include "encrypt.h"
void encrypt(char *message)
{
// char c; errata
while (*message) {
*message = *message ^ 31;
message++;
}
}
message_hider.c:
#include <stdio.h>
#include "encrypt.h"
int main() {
char msg[80];
while (fgets(msg, 80, stdin)) {
encrypt(msg);
printf("%s", msg);
}
}
NOW, everything works fine IF I faithfully compile as per exercise instruction:
gcc message_hider.c encrypt.c -o message_hider
... but bad fortune led me to compile only the main .c file, like so:
$ gcc message_hider.c -o message_hider
This surprisingly successfully builds, even if I added -Wall -Wextra -Wshadow -g.
Also surprisingly, it silently fails, with no output from encrypt() function:
$ ./message_hider < ./encrypt.h
$
my gcc is:
$ /usr/bin/gcc --version
Apple clang version 13.1.6 (clang-1316.0.21.2.5)
Target: x86_64-apple-darwin21.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
Mindful that even with a Makefile, I could "still" end up with a missing .c file due to a mistake in the recipe.
Q: Is it possible to force a hard error if I forget to tell gcc about a .c file?
As I noted in a (misspelled) comment:
There is probably a function encrypt() in the system library.
On a Mac, man -s 3 encrypt shows:
CRYPT(3) BSD Library Functions Manual CRYPT(3)
NAME
crypt, encrypt, setkey -- DES encryption
SYNOPSIS
#include <unistd.h>
char *
crypt(const char *key, const char *salt);
void
encrypt(char *block, int edflag);
#include <stdlib.h>
void
setkey(const char *key);
…
The encrypt() and setkey() functions are part of POSIX, so they'll be available on most POSIX-like systems. Curiously, as shown in the manual page extract, the functions are declared in separate headers — <unistd.h> for encrypt() and
<stdlib.h> for setkey(). There's probably a good (enough) historical reason for the disconnect.
You should have received a compiler warning about the function being undeclared — if you didn't, you are presumably compiling using the C90 standard. That is very old and should not still be being taught; you need to be learning C11 or C18 (almost the same).
Since C99, the C standard requires functions to be declared before use — you can define a static function without pre-declaring it, but all other functions (except main()) should be declared before they are used or defined. You can use GCC compiler warning options such as -Wmissing-prototypes -Wstrict-prototypes (along with -Wold-style-declaration and -Wold-style-definition) to trigger warnings. Of these, -Wold-style-declaration is enabled by -Wextra (and none by -Wall). Be aware: as noted in the comments, clang does not support -Wold-style-declaration though true GCC (not Apple's clang masquerading as gcc) does support it.

Why does printf specifier format %n not work?

This is my code:
#include <stdio.h>
int main(void) {
int n;
fprintf(stdout, "Hello%n World\n", &n);
fprintf(stdout, "n: %d\n", n);
return 0;
}
This is my output:
Hellon: 0
Why does the fprintf format specifier "%n" not work?
Why is the string to be printed interrupted?
ISO/IEC 9899:201x C11 - 7.21.6.1 - The fprintf function
The conversion specifiers and their meanings are:
(...)
%n The argument shall be a pointer to signed integer into which is written the number of characters written to the output stream so far
by this call to fprintf. No argument is converted, but one is
consumed. If the conversion specification includes any flags, a field
width, or a precision, the behavior is undefined. ...
(...)
This is my compiler version used on Code::Blocks:
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=C:/Program\ Files/mingw-w64/x86_64-8.1.0-posix-seh-rt_v6-rev
0/mingw64/bin/../libexec/gcc/x86_64-w64-mingw32/8.1.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../../../src/gcc-8.1.0/configure --host=x86_64-w64-mingw32 --bu
ild=x86_64-w64-mingw32 --target=x86_64-w64-mingw32 --prefix=/mingw64 --with-sysr
oot=/c/mingw810/x86_64-810-posix-seh-rt_v6-rev0/mingw64 --enable-shared --enable
-static --disable-multilib --enable-languages=c,c++,fortran,lto --enable-libstdc
xx-time=yes --enable-threads=posix --enable-libgomp --enable-libatomic --enable-
lto --enable-graphite --enable-checking=release --enable-fully-dynamic-string --
enable-version-specific-runtime-libs --disable-libstdcxx-pch --disable-libstdcxx
-debug --enable-bootstrap --disable-rpath --disable-win32-registry --disable-nls
--disable-werror --disable-symvers --with-gnu-as --with-gnu-ld --with-arch=noco
na --with-tune=core2 --with-libiconv --with-system-zlib --with-gmp=/c/mingw810/p
rerequisites/x86_64-w64-mingw32-static --with-mpfr=/c/mingw810/prerequisites/x86
_64-w64-mingw32-static --with-mpc=/c/mingw810/prerequisites/x86_64-w64-mingw32-s
tatic --with-isl=/c/mingw810/prerequisites/x86_64-w64-mingw32-static --with-pkgv
ersion='x86_64-posix-seh-rev0, Built by MinGW-W64 project' --with-bugurl=https:/
/sourceforge.net/projects/mingw-w64 CFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/x
86_64-810-posix-seh-rt_v6-rev0/mingw64/opt/include -I/c/mingw810/prerequisites/x
86_64-zlib-static/include -I/c/mingw810/prerequisites/x86_64-w64-mingw32-static/
include' CXXFLAGS='-O2 -pipe -fno-ident -I/c/mingw810/x86_64-810-posix-seh-rt_v6
-rev0/mingw64/opt/include -I/c/mingw810/prerequisites/x86_64-zlib-static/include
-I/c/mingw810/prerequisites/x86_64-w64-mingw32-static/include' CPPFLAGS=' -I/c/
mingw810/x86_64-810-posix-seh-rt_v6-rev0/mingw64/opt/include -I/c/mingw810/prere
quisites/x86_64-zlib-static/include -I/c/mingw810/prerequisites/x86_64-w64-mingw
32-static/include' LDFLAGS='-pipe -fno-ident -L/c/mingw810/x86_64-810-posix-seh-
rt_v6-rev0/mingw64/opt/lib -L/c/mingw810/prerequisites/x86_64-zlib-static/lib -L
/c/mingw810/prerequisites/x86_64-w64-mingw32-static/lib '
Thread model: posix
gcc version 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)
As documented in the Microsoft documentation, the %n is disabled by default in the Microsoft C library used on your MinGW system:
Important
Because the %n format is inherently insecure, it is disabled by default. If %n is encountered in a format string, the invalid parameter handler is invoked, as described in Parameter Validation. To enable %n support, see _set_printf_count_output.
Whether %n is actually unsafe as claimed by Microsoft is highly debatable. The examples shown to support this claim combine this printf function with the use of a variable format string that can by changed by the attacker via a buffer overflow error.
On some Microsoft systems (but maybe not the latest), you could fix your program this way:
#include <stdio.h>
int main(void) {
int n;
_set_printf_count_output(1);
fprintf(stdout, "Hello%n World\n", &n);
fprintf(stdout, "n: %d\n", n);
return 0;
}
For a more portable approach, here is a work around to avoid using %n and still get the same results:
#include <stdio.h>
int main(void) {
int n;
n = fprintf(stdout, "Hello");
fprintf(stdout, " World\n");
fprintf(stdout, "n: %d\n", n);
return 0;
}
Output:
Hello World
n: 5

gcc: __fread_chk_warn warning

I have a compilation problem with gcc.
Assume the following program:
#include <stdio.h>
int test(const char *fname) {
FILE *fh = fopen(fname, "rb");
int tmp;
if (fread(&tmp, sizeof(tmp), 1, fh) < 1) {
tmp = 0;
}
fclose(fh);
return tmp;
}
int main(void) {
printf("%d\n", test("test.txt"));
return 0;
}
And the file test.txt:
11111111111111111111111111111111...
Of course, the program is very stupid, but it works:
user#ubuntu:~/tmp/optxx$ gcc -O3 -Wall -Wextra test.c
user#ubuntu:~/tmp/optxx$ ./a.out
825307441
Let's modify it a bit (only add an attribute to the test-function):
#include <stdio.h>
int __attribute__((optimize("O0"))) test(const char *fname) {
FILE *fh = fopen(fname, "rb");
int tmp;
if (fread(&tmp, sizeof(tmp), 1, fh) < 1) {
tmp = 0;
}
fclose(fh);
return tmp;
}
int main(void) {
printf("%d\n", test("test.txt"));
return 0;
}
The function test should no longer be optimized. But now compilation fails:
user#ubuntu:~/tmp/optxx$ gcc -Wall -Wextra -O3 test.c
In file included from /usr/include/stdio.h:936:0,
from test.c:1:
In function ‘fread’,
inlined from ‘test’ at test.c:6:9:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:293:9: warning: call to ‘__fread_chk_warn’ declared with attribute warning: fread called with bigger size * nmemb than length of destination buffer
return __fread_chk_warn (__ptr, __bos0 (__ptr), __size, __n, __stream);
^
I get a warning and normally i compile with -Werror, so i don't like warnings.
The used gcc version:
user#ubuntu:~/tmp/optxx$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.3.1-6ubuntu2' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 5.3.1 20160119 (Ubuntu 5.3.1-6ubuntu2)
Unfortunately i am not able to fix the warning:(
Maybe someone has an idea why this happens?
-edit-
A (dirty) hack would also be okay to remove this warning:)
-edit-
Probably something with my linux is wrong, because it seems to work for everyone else. Anayway a dirty hack / fix would be to add this code before the fread call:
static size_t fread_wrapper(void *ptr, size_t size, size_t count, FILE *stream) {
return fread(ptr, size, count, stream);
}
#define fread(ptr, size, count, stream) fread_wrapper((ptr), (size), (count), (stream))
Ubuntu enable the _FORTIFY_SOURCE feature by default for optimization levels -O1 or higher. This option makes the system headers pre-process to use different functions for fread among others. Those functions perform some basic safety checks of arguments. The option gets enabled globally when the header is included so it makes the assumption that all the code will be compiled with -O1 or higher. I assume that it is disabled on lower optimization levels because gcc doesn't have enough information without some optimizations and that causes those magic fortify macros to generate false positives (like you got).
If you compile the whole file with -O0, _FORTIFY_SOURCE is disabled and things will work. Alternatively I guess you can compile the whole file with -D_FORTIFY_SOURCE=0, although I haven't tried it.
Also, I could reproduce this on other flavors of linux by just adding -D_FORTIFY_SOURCE=1.
I guess you can call it a compiler/glibc/Ubuntu bug. Or just stop using crazy optimization attributes. The world can't be tested with all possible weird combinations so we should exercise caution when pushing buttons and turning knobs.

How can I compile C programs that use deprecated functions, such as gets()

For a homework assignment, I have to try to use buffer overflows to crash a simple c program. My problem is that my compiler won't compile the gets() function because it is deprecated and unsafe. I understand this, but for the sake of the example, I'd like to override that. Here is the code:
#include <stdio.h>
int main(int argc, char *argv[]) {
int valid = 0;
// Char arrays w/buffer set to 8 chars
char str1[8];
char str2[8];
next_tag(str1);
//This is where I want to use gets and not fgets or other secure functions...
gets(str2);
if(strncmp(str1, str2, 8) == 0) {
valid = 1;
}
// Print
printf("Buffer 1: str1(%s), str2(%s), valid(%d)\n", str1, str2, valid);
}
My gcc version is:
rabbitfighter#ECHO:[~/Code/C/BufferOverflowExamples]: gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-unknown-linux-gnu/4.9.1/lto-wrapper
Target: x86_64-unknown-linux-gnu
Configured with: /build/gcc/src/gcc-4.9-20140903/configure --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.archlinux.org/ --enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++ --enable-shared --enable-threads=posix --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-clocale=gnu --disable-libstdcxx-pch --disable-libssp --enable-gnu-unique-object --enable-linker-build-id --enable-cloog-backend=isl --disable-isl-version-check --disable-cloog-version-check --enable-lto --enable-plugin --enable-install-libiberty --with-linker-hash-style=gnu --disable-multilib --disable-werror --enable-checking=release
Thread model: posix
gcc version 4.9.1 20140903 (prerelease) (GCC)
If anyone can help me I would greatly appreciate it. I am running Manjaro Linux (Arch).
You can use a "safer" funcion like fgets and give it a bogus buffer size to get unsafe behavior out of it.
A larger buffer size than the real buffer will let fgets overrun its bounds and potentially crash the program.
Just because the gun has a safety doesn't mean you can't shoot your foot off with it.

in6_addr in GCC 4

I see code like following : (legecy code in the project I am working on)
#if __GNUC__ > 3
.ipv6_addr = {.__in6_u = {.__u6_addr32 = {0, 0, 0, 0}}}
#else
.ipv6_addr = {.in6_u = {.u6_addr32 = {0, 0, 0, 0}}}
#endif
where "ipv6_addr" is in type of struct in6_addr. I don't understand why its member in6_u would change to "in6_u" if __GNUC > 3.
My question is: why / when GCC version could impact the name of the field in struct in6_addr ?
Thanks.
Update: my host system has GCC 4.1.2, but the in6_addr was defined as :
in /usr/include/netinet/in.h
/* IPv6 address */
struct in6_addr
{
union
{
uint8_t u6_addr8[16];
uint16_t u6_addr16[8];
uint32_t u6_addr32[4];
} in6_u;
#define s6_addr in6_u.u6_addr8
#define s6_addr16 in6_u.u6_addr16
#define s6_addr32 in6_u.u6_addr32
};
And gcc version is:
$/usr/bin/gcc -v
Using built-in specs.
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-libgcj-multifile --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --disable-plugin --with-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --with-cpu=generic --host=x86_64-redhat-linux
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-50)
The structure definition of concern here, struct in6_addr, comes from the C runtime library (in <netinet/in.h>). The C runtime library is usually tightly coupled with the operating system in use, but not the C compiler in use. This is especially true for the C compilers that define __GNUC__ to any value (there are at least three of these: GCC, clang, and icc); these compilers are designed to be usable with many different OSes and runtimes. Therefore, in principle, testing __GNUC__ does not tell you anything useful about structure definitions that come from the runtime.
I suspect that the authors of this "legacy code" tested on two different Linux distributions, noted an accidental correlation between the value of __GNUC__ and the contents of <netinet/in.h>, and didn't bother looking for a more correct way to make their code compile.
You should replace the entire conditional with this:
.ipv6_addr = IN6ADDR_ANY_INIT;
The macro IN6ADDR_ANY_INIT is required by the relevant standard (POSIX.1-2008 spec for <netinet/in.h>) to be usable as an initializer for a variable of type in6_addr, setting that variable to the IPv6 wildcard address, which is all-bits-zero. So it will have the same effect without requiring any #ifdefs at all.
To illustrate that #if __GNUC__ > 3 is the wrong test to apply here, here are three different definitions of struct in6_addr, all taken from systems where you might reasonably encounter both __GNUC_==3 and __GNUC__==4 (the 3.x series is getting a little old nowadays, but I still run into it from time to time).
GNU libc 2.17
struct in6_addr
{
union
{
uint8_t __u6_addr8[16];
#if defined __USE_MISC || defined __USE_GNU
uint16_t __u6_addr16[8];
uint32_t __u6_addr32[4];
#endif
} __in6_u;
};
NetBSD 6.1
struct in6_addr {
union {
__uint8_t __u6_addr8[16];
__uint16_t __u6_addr16[8];
uint32_t __u6_addr32[4];
} __u6_addr; /* 128-bit IP6 address */
};
Windows 7 (official SDK; perversely does not provide netinet/in.h):
struct in6_addr {
union {
UCHAR Byte[16];
USHORT Word[8];
} u;
};

Resources