Defending against unsafe coding practices with "libsafe"

In a previous tip about securing Linux applications with compiler extensions, we described a defense-in-depth layered methodology ("defense in depth") to proactively mitigate the potential

Requires Free Membership to View

for risk or damage arising from fatally-flawed programming constructs.

In this article, a second layer is introduced to add much-needed boundaries to checking to compiled C binaries, so as to produce robust, reliable applications capable of withstanding punishment from would-be attackers who try to break them.

The problem with compiler extensions is that they require a manual recompile of the code for the compiler itself, followed by recompilation of system binaries, to be truly effective. This painstaking and tedious process does not lend itself well to rapid deployment and thus, leaves much to be desired.

Enter libsafe, an all-purpose application defense mechanism that intercepts known-vulnerable library calls for pre-compiled binaries. By coercing any given vulnerable function call into a segregated stack frame, libsafe ensures that any potential for damage caused by errant code is safely contained within well-defined defenses. By providing a run-time protection mechanism, libsafe can do things that a fortified compiler suite cannot -- especially when it comes to setting upper bounds on the sizes for dynamically allocated buffers (or those buffers whose size isn't known at the time of compilation).

Because so many software developers apparently don't apply standard base and bounds checks on the code they build, too many buffer overflow attacks spawn actual exploits. Fortunately, the open source libsafe tool shuts down holes that others leave open.

In short, libsafe sits inline between suspect dynamic library calls and the applications that call upon them and makes sure that no function call oversteps its own stack-frame boundaries. This is usually called function-call interposition, because it effectively interposes the libsafe call-hooking functionality between calling and called functions.

Here's a list of the functions that libsafe monitors:

  • strcpy(char *dest, const char *src)
  • strcpy(char *dest, const char *src)
  • strpcpy(char *dest, const char *src)
  • wcscpy(wchar_t *dest, const wchar_t *src)
  • wcpcpy(wchar_t *dest, const wchar_t *src)
  • strcat(char *dest, const char *src)
  • wcscpy(wchar_t *dest, const wchar_t *src)
  • getwd(char *buf)
  • gets(char *s)
  • fscanf(const char *format, ...)
  • vscanf(const char *, …)
  • realpath(char *path, char resolved_path[])
  • sprintf(char *, const char *, ...)
  • vsprintf(char *, const char *, …)

Using the libsafe call protection scheme couldn't be simpler and takes only a moment to implement. After compiling the package just like any other Linux source archive, introduce the libsafe shared library component into the runtime environment by exporting it through the dynamic linker/loader as shown below:

# export LD_PRELOAD=/lib/libsafe.so.2

This single entry may be added to your shell profile to make it readily available during each login session. Then, to begin using libsafe (or to verify its correctness) invoke any program that you suspect or know to be faulty as illustrated here:

libsafe.so[34]: detected an attempt to write across stack boundary.
libsafe.so[34]: terminating /home/zjin/libsafe/crash1
libsafe.so[34]: overflow caused by strcpy()
libsafe.so[53]: detected an attempt to write across stack boundary.
libsafe.so[53]: terminating /home/zjin/libsafe/crash2
libsafe.so[53]: overflow caused by memcpy()

Those of you keen on dynamic linker behavior will be quick to note that set user ID (setuid) programs are disregarded for security reasons. However, setuid binaries can still get this same treatment from libsafe if they're exported through proper channels -- namely, by appending the path to your libsafe.so.2 shared object to the /etc/ld.so.preload file.

While using both stack-protective compiler extensions and the libsafe component in tandem provides adequate coverage against many common security threats, these solutions treat symptoms rather than causes. If unsafe programming practices are avoided to begin with, neither approach is needed. But until we see a market-wide improvement in the way that commercial vendors educate their developers, and universal adoption of common checks to prevent buffer overflows from occurring, we recommend that you deploy these kinds of preventive measures to safeguard against potential vulnerabilities.

About the authors: Ed Tittel is a full-time freelance writer and trainer based in Austin, Tex., who specializes in markup languages, information security and IT certifications. Justin Korelc is a long-time Linux hacker who works with Ed and concentrates on hardware and software security topics. Together, both contributed to a recent book on Home Theater PCs and the Tom's Hardware 2005 Holiday Buyer's Guide.

This was first published in January 2006

There are Comments. Add yours.

TIP: Want to include a code block in your comment? Use <pre> or <code> tags around the desired text. Ex: <code>insert code</code>

REGISTER or login:

Forgot Password?
By submitting you agree to receive email from TechTarget and its partners. If you reside outside of the United States, you consent to having your personal data transferred to and processed in the United States. Privacy
Sort by: OldestNewest

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

Disclaimer: Our Tips Exchange is a forum for you to share technical advice and expertise with your peers and to learn from other enterprise IT professionals. TechTarget provides the infrastructure to facilitate this sharing of information. However, we cannot guarantee the accuracy or validity of the material submitted. You agree that your use of the Ask The Expert services and your reliance on any questions, answers, information or other materials received through this Web site is at your own risk.