I’m an expert on how technology hijacks our psychological vulnerabilities. That’s why I spent the last three years as a Design Ethicist at Google caring about how to design things in a way that defends a billion people’s minds from getting hijacked.
When using technology, we often focus optimistically on all the things it does for us. But I want to show you where it might do the opposite.
Where does technology exploit our minds’ weaknesses?
I learned to think this way when I was a magician. Magicians start by looking for blind spots, edges, vulnerabilities and limits of people’s perception, so they can influence what people do without them even realizing it. Once you know how to push people’s buttons, you can play them like a piano.
That’s me performing sleight of hand magic at my mother’s birthday party
And this is exactly what product designers do to your mind. They play your psychological vulnerabilities (consciously and unconsciously) against you in the race to grab your attention.
I want to show you how they do it.
Hijack #1: If You Control the Menu, You Control the Choices
Western Culture is built around ideals of individual choice and freedom. Millions of us fiercely defend our right to make “free” choices, while we ignore how those choices are manipulated upstream by menus we didn’t choose in the first place.
This is exactly what magicians do. They give people the illusion of free choice while architecting the menu so that they win, no matter what you choose. I can’t emphasize enough how deep this insight is.
When people are given a menu of choices, they rarely ask:
“what’s not on the menu?”
“why am I being given these options and not others?”
“do I know the menu provider’s goals?”
“is this menu empowering for my original need, or are the choices actually a distraction?” (e.g. an overwhelmingly array of toothpastes)
How empowering is this menu of choices for the need, “I ran out of toothpaste”?
For example, imagine you’re out with friends on a Tuesday night and want to keep the conversation going. You open Yelp to find nearby recommendations and see a list of bars. The group turns into a huddle of faces staring down at their phones comparing bars. They scrutinize the photos of each, comparing cocktail drinks. Is this menu still relevant to the original desire of the group?
It’s not that bars aren’t a good choice, it’s that Yelp substituted the group’s original question (“where can we go to keep talking?”) with a different question (“what’s a bar with good photos of cocktails?”) all by shaping the menu.
Moreover, the group falls for the illusion that Yelp’s menu represents a complete set of choices for where to go. While looking down at their phones, they don’t see the park across the street with a band playing live music. They miss the pop-up gallery on the other side of the street serving crepes and coffee. Neither of those show up on Yelp’s menu.
Yelp subtly reframes the group’s need “where can we go to keep talking?” in terms of photos of cocktails served.
The more choices technology gives us in nearly every domain of our lives (information, events, places to go, friends, dating, jobs) — the more we assume that our phone is always the most empowering and useful menu to pick from. Is it?
The “most empowering” menu is different than the menu that has the most choices. But when we blindly surrender to the menus we’re given, it’s easy to lose track of the difference:
“Who’s free tonight to hang out?” becomes a menu of most recent people who texted us (who we could ping).
“What’s happening in the world?” becomes a menu of news feed stories.
“Who’s single to go on a date?” becomes a menu of faces to swipe on Tinder (instead of local events with friends, or urban adventures nearby).
“I have to respond to this email.” becomes a menu of keys to type a response (instead of empowering ways to communicate with a person).
All user interfaces are menus. What if your email client gave you empowering choices of ways to respond, instead of “what message do you want to type back?” (Design by Tristan Harris)
When we wake up in the morning and turn our phone over to see a list of notifications — it frames the experience of “waking up in the morning” around a menu of “all the things I’ve missed since yesterday.” (for more examples, see Joe Edelman’s Empowering Design talk)
A list of notifications when we wake up in the morning — how empowering is this menu of choices when we wake up? Does it reflect what we care about? (from Joe Edelman’s Empowering Design Talk)
By shaping the menus we pick from, technology hijacks the way we perceive our choices and replaces them with new ones. But the closer we pay attention to the options we’re given, the more we’ll notice when they don’t actually align with our true needs.
Hijack #2: Put a Slot Machine In a Billion Pockets
If you’re an app, how do you keep people hooked? Turn yourself into a slot machine.
The average person checks their phone 150 times a day. Why do we do this? Are we making 150 conscious choices?
How often do you check your email per day?
One major reason why is the #1 psychological ingredient in slot machines: intermittent variable rewards.
If you want to maximize addictiveness, all tech designers need to do is link a user’s action (like pulling a lever) with a variable reward. You pull a lever and immediately receive either an enticing reward (a match, a prize!) or nothing. Addictiveness is maximized when the rate of reward is most variable.
Does this effect really work on people? Yes. Slot machines make more money in the United States than baseball, movies, and theme parks combined. Relative to other kinds of gambling, people get ‘problematically involved’ with slot machines 3–4x faster according to NYU professor Natasha Dow Schull, author of Addiction by Design.
But here’s the unfortunate truth — several billion people have a slot machine their pocket:
When we pull our phone out of our pocket, we’re playing a slot machine to see what notifications we got.
When we pull to refresh our email, we’re playing a slot machine to see what new email we got.
When we swipe down our finger to scroll the Instagram feed, we’re playing a slot machine to see what photo comes next.
When we swipe faces left/right on dating apps like Tinder, we’re playing a slot machine to see if we got a match.
When we tap the # of red notifications, we’re playing a slot machine to what’s underneath.
Apps and websites sprinkle intermittent variable rewards all over their products because it’s good for business.
But in other cases, slot machines emerge by accident. For example, there is no malicious corporation behind all of email who consciously chose to make it a slot machine. No one profits when millions check their email and nothing’s there. Neither did Apple and Google’s designers want phones to work like slot machines. It emerged by accident.
But now companies like Apple and Google have a responsibility to reduce these effects by converting intermittent variable rewards into less addictive, more predictable ones with better design. For example, they could empower people to set predictable times during the day or week for when they want to check “slot machine” apps, and correspondingly adjust when new messages are delivered to align with those times.
Hijack #3: Fear of Missing Something Important (FOMSI)
Another way apps and websites hijack people’s minds is by inducing a “1% chance you could be missing something important.”
If I convince you that I’m a channel for important information, messages, friendships, or potential sexual opportunities — it will be hard for you to turn me off, unsubscribe, or remove your account — because (aha, I win) you might miss something important:
This keeps us subscribed to newsletters even after they haven’t delivered recent benefits (“what if I miss a future announcement?”)
This keeps us “friended” to people with whom we haven’t spoke in ages (“what if I miss something important from them?”)
This keeps us swiping faces on dating apps, even when we haven’t even met up with anyone in a while (“what if I miss that one hot match who likes me?”)
This keeps us using social media (“what if I miss that important news story or fall behind what my friends are talking about?”)
But if we zoom into that fear, we’ll discover that it’s unbounded: we’ll always miss something important at any point when we stop using something.
There are magic moments on Facebook we’ll miss by not using it for the 6th hour (e.g. an old friend who’s visiting town right now).
There are magic moments we’ll miss on Tinder (e.g. our dream romantic partner) by not swiping our 700th match.
There are emergency phone calls we’ll miss if we’re not connected 24/7.
But living moment to moment with the fear of missing something isn’t how we’re built to live.
And it’s amazing how quickly, once we let go of that fear, we wake up from the illusion. When we unplug for more than a day, unsubscribe from those notifications, or go to Camp Grounded — the concerns we thought we’d have don’t actually happen.
We don’t miss what we don’t see.
The thought, “what if I miss something important?” is generated in advance of unplugging, unsubscribing, or turning off — not after. Imagine if tech companies recognized that, and helped us proactively tune our relationships with friends and businesses in terms of what we define as “time well spent” for our lives, instead of in terms of what we might miss.
Hijack #4: Social Approval
Easily one of the most persuasive things a human being can receive.
We’re all vulnerable to social approval. The need to belong, to be approved or appreciated by our peers is among the highest human motivations. But now our social approval is in the hands of tech companies.
When I get tagged by my friend Marc, I imagine him making a conscious choice to tag me. But I don’t see how a company like Facebook orchestrated his doing that in the first place.
Facebook, Instagram or SnapChat can manipulate how often people get tagged in photos by automatically suggesting all the faces people should tag (e.g. by showing a box with a 1-click confirmation, “Tag Tristan in this photo?”).
So when Marc tags me, he’s actually responding to Facebook’s suggestion, not making an independent choice. But through design choices like this, Facebook controls the multiplier for how often millions of people experience their social approval on the line.
Facebook uses automatic suggestions like this to get people to tag more people, creating more social externalities and interruptions.
The same happens when we change our main profile photo — Facebook knows that’s a moment when we’re vulnerable to social approval: “what do my friends think of my new pic?” Facebook can rank this higher in the news feed, so it sticks around for longer and more friends will like or comment on it. Each time they like or comment on it, we’ll get pulled right back.
Everyone innately responds to social approval, but some demographics (teenagers) are more vulnerable to it than others. That’s why it’s so important to recognize how powerful designers are when they exploit this vulnerability.
Hijack #5: Social Reciprocity (Tit-for-tat)
You do me a favor — I owe you one next time.
You say, “thank you”— I have to say “you’re welcome.”
You send me an email— it’s rude not to get back to you.
You follow me — it’s rude not to follow you back. (especially for teenagers)
We are vulnerable to needing to reciprocate others’ gestures. But as with Social Approval, tech companies now manipulate how often we experience it.
In some cases, it’s by accident. Email, texting and messaging apps are social reciprocity factories. But in other cases, companies exploit this vulnerability on purpose.
LinkedIn is the most obvious offender. LinkedIn wants as many people creating social obligations for each other as possible, because each time they reciprocate (by accepting a connection, responding to a message, or endorsing someone back for a skill) they have to come back to linkedin.com where they can get people to spend more time.
Like Facebook, LinkedIn exploits an asymmetry in perception. When you receive an invitation from someone to connect, you imagine that person making a conscious choice to invite you, when in reality, they likely unconsciously responded to LinkedIn’s list of suggested contacts. In other words, LinkedIn turns your unconscious impulses (to “add” a person) into new social obligations that millions of people feel obligated to repay. All while they profit from the time people spend doing it.
Imagine millions of people getting interrupted like this throughout their day, running around like chickens with their heads cut off, reciprocating each other — all designed by companies who profit from it.
Welcome to social media.
After accepting an endorsement, LinkedIn takes advantage of your bias to reciprocate by offering four additional people for you to endorse in return.
Imagine if technology companies had a responsibility to minimize social reciprocity. Or if there was an independent organization that represented the public’s interests — an industry consortium or an FDA for tech — that monitored when technology companies abused these biases?
Hijack #6: Bottomless bowls, Infinite Feeds, and Autoplay
YouTube autoplays the next video after a countdown
Another way to hijack people is to keep them consuming things, even when they aren’t hungry anymore.
How? Easy. Take an experience that was bounded and finite, and turn it into a bottomless flow that keeps going.
Cornell professor Brian Wansink demonstrated this in his study showing you can trick people into keep eating soup by giving them a bottomless bowl that automatically refills as they eat. With bottomless bowls, people eat 73% more calories than those with normal bowls and underestimate how many calories they ate by 140 calories.
Tech companies exploit the same principle. News feeds are purposely designed to auto-refill with reasons to keep you scrolling, and purposely eliminate any reason for you to pause, reconsider or leave.
It’s also why video and social media sites like Netflix, YouTube or Facebook autoplay the next video after a countdown instead of waiting for you to make a conscious choice (in case you won’t). A huge portion of traffic on these websites is driven by autoplaying the next thing.
Facebook autoplays the next video after a countdown
Tech companies often claim that “we’re just making it easier for users to see the video they want to watch” when they are actually serving their business interests. And you can’t blame them, because increasing “time spent” is the currency they compete for.
Instead, imagine if technology companies empowered you to consciously bound your experience to align with what would be “time well spent” for you. Not just bounding the quantity of time you spend, but the qualities of what would be “time well spent.”
Hijack #7: Instant Interruption vs. “Respectful” Delivery
Companies know that messages that interrupt people immediately are more persuasive at getting people to respond than messages delivered asynchronously (like email or any deferred inbox).
Given the choice, Facebook Messenger (or WhatsApp, WeChat or SnapChat for that matter) would prefer to design their messaging system to interrupt recipients immediately (and show a chat box) instead of helping users respect each other’s attention.
In other words, interruption is good for business.
It’s also in their interest to heighten the feeling of urgency and social reciprocity. For example, Facebook automatically tells the sender when you “saw” their message, instead of letting you avoid disclosing whether you read it (“now that you know I’ve seen the message, I feel even more obligated to respond.”)
By contrast, Apple more respectfully lets users toggle “Read Receipts” on or off.
The problem is, maximizing interruptions in the name of business creates a tragedy of the commons, ruining global attention spans and causing billions of unnecessary interruptions each day. This is a huge problem we need to fix with shared design standards (potentially, as part of Time Well Spent).
Hijack #8: Bundling Your Reasons with Their Reasons
Another way apps hijack you is by taking your reasons for visiting the app (to perform a task) and make them inseparable from the app’s business reasons (maximizing how much we consume once we’re there).
For example, in the physical world of grocery stores, the #1 and #2 most popular reasons to visit are pharmacy refills and buying milk. But grocery stores want to maximize how much people buy, so they put the pharmacy and the milk at the back of the store.
In other words, they make the thing customers want (milk, pharmacy) inseparable from what the business wants. If stores were truly organized to support people, they would put the most popular items in the front.
Tech companies design their websites the same way. For example, when you you want to look up a Facebook event happening tonight (your reason) the Facebook app doesn’t allow you to access it without first landing on the news feed (their reasons), and that’s on purpose. Facebook wants to convert every reason you have for using Facebook, into their reason which is to maximize the time you spend consuming things.
Instead, imagine if …
Twitter gave you a separate way to post an Tweet than having to see their news feed.
Facebook gave a separate way to look up Facebook Events going on tonight, without being forced to use their news feed.
Facebook gave you a separate way to use Facebook Connect as a passport for creating new accounts on 3rd party apps and websites, without being forced to install Facebook’s entire app, news feed and notifications.
In a Time Well Spent world, there is always a direct way to get what you want separately from what businesses want. Imagine a digital “bill of rights” outlining design standards that forced the products used by billions of people to let them navigate directly to what they want without needing to go through intentionally placed distractions.
Imagine if web browsers empowered you to navigate directly to what you want — especially for sites that intentionally detour you toward their reasons.
Hijack #9: Inconvenient Choices
We’re told that it’s enough for businesses to “make choices available.”
“If you don’t like it you can always use a different product.”
“If you don’t like it, you can always unsubscribe.”
“If you’re addicted to our app, you can always uninstall it from your phone.”
Businesses naturally want to make the choices they want you to make easier, and the choices they don’t want you to make harder. Magicians do the same thing. You make it easier for a spectator to pick the thing you want them to pick, and harder to pick the thing you don’t.
For example, NYTimes.com lets you “make a free choice” to cancel your digital subscription. But instead of just doing it when you hit “Cancel Subscription,” they send you an email with information on how to cancel your account by calling a phone number that’s only open at certain times.
NYTimes claims it’s giving a free choice to cancel your account
Instead of viewing the world in terms of availability of choices, we should view the world in terms of friction required to enact choices. Imagine a world where choices were labeled with how difficult they were to fulfill (like coefficients of friction) and there was an independent entity — an industry consortium or non-profit — that labeled these difficulties and set standards for how easy navigation should be.
Hijack #10: Forecasting Errors, “Foot in the Door” strategies
Facebook promises an easy choice to “See Photo.” Would we still click if it gave the true price tag?
Lastly, apps can exploit people’s inability to forecast the consequences of a click.
People don’t intuitively forecast the true cost of a click when it’s presented to them. Sales people use “foot in the door” techniques by asking for a small innocuous request to begin with (“just one click to see which tweet got retweeted”) and escalate from there (“why don’t you stay awhile?”). Virtually all engagement websites use this trick.
Imagine if web browsers and smartphones, the gateways through which people make these choices, were truly watching out for people and helped them forecast the consequences of clicks (based on real data about what benefits and costs it actually had?).
That’s why I add “Estimated reading time” to the top of my posts. When you put the “true cost” of a choice in front of people, you’re treating your users or audience with dignity and respect. In a Time Well Spent internet, choices could be framed in terms of projected cost and benefit, so people were empowered to make informed choices by default, not by doing extra work.
TripAdvisor uses a “foot in the door” technique by asking for a single click review (“How many stars?”) while hiding the three page survey of questions behind the click.
Summary And How We Can Fix This
Are you upset that technology hijacks your agency? I am too. I’ve listed a few techniques but there are literally thousands. Imagine whole bookshelves, seminars, workshops and trainings that teach aspiring tech entrepreneurs techniques like these. Imagine hundreds of engineers whose job every day is to invent new ways to keep you hooked.
The ultimate freedom is a free mind, and we need technology that’s on our team to help us live, feel, think and act freely.
We need our smartphones, notifications screens and web browsers to be exoskeletons for our minds and interpersonal relationships that put our values, not our impulses, first. People’s time is valuable. And we should protect it with the same rigor as privacy and other digital rights.
"Delivering Signals for Fun and Profit"
Understanding, exploiting and preventing signal-handling
related vulnerabilities.
Michal Zalewski <lcamtuf@razor.bindview.com>
(C) Copyright 2001 BindView Corporation
According to a popular belief, writing signal handlers has little or nothing
to do with secure programming, as long as handler code itself looks good.
At the same time, there have been discussions on functions that shall be
invoked from handlers, and functions that shall never, ever be used there.
Most Unix systems provide a standarized set of signal-safe library calls.
Few systems have extensive documentation of signal-safe calls - that includes
OpenBSD, Solaris, etc.:
http://www.openbsd.org/cgi-bin/man.cgi?query=sigaction:
"The following functions are either reentrant or not interruptible by sig-
nals and are async-signal safe. Therefore applications may invoke them,
without restriction, from signal-catching functions:
_exit(2), access(2), alarm(3), cfgetispeed(3), cfgetospeed(3),
cfsetispeed(3), cfsetospeed(3), chdir(2), chmod(2), chown(2),
close(2), creat(2), dup(2), dup2(2), execle(2), execve(2),
fcntl(2), fork(2), fpathconf(2), fstat(2), fsync(2), getegid(2),
geteuid(2), getgid(2), getgroups(2), getpgrp(2), getpid(2),
getppid(2), getuid(2), kill(2), link(2), lseek(2), mkdir(2),
mkfifo(2), open(2), pathconf(2), pause(2), pipe(2), raise(3),
read(2), rename(2), rmdir(2), setgid(2), setpgid(2), setsid(2),
setuid(2), sigaction(2), sigaddset(3), sigdelset(3),
sigemptyset(3), sigfillset(3), sigismember(3), signal(3),
sigpending(2), sigprocmask(2), sigsuspend(2), sleep(3), stat(2),
sysconf(3), tcdrain(3), tcflow(3), tcflush(3), tcgetattr(3),
tcgetpgrp(3), tcsendbreak(3), tcsetattr(3), tcsetpgrp(3), time(3),
times(3), umask(2), uname(3), unlink(2), utime(3), wait(2),
waitpid(2), write(2). sigpause(3), sigset(3).
All functions not in the above list are considered to be unsafe with re-
spect to signals. That is to say, the behaviour of such functions when
called from a signal handler is undefined. In general though, signal
handlers should do little more than set a flag; most other actions are
not safe."
It is suggested to take special care when performing any non-atomic
operations while signal delivery is not blocked, and/or not to rely on
internal program state in signal handler. Generally, signal handlers should
do not much more than setting a flag, whenever it is acceptable.
Unfortunately, there were no known, practical security considerations of
such bad coding practices. And while signal can be delivered anywhere
during the userspace execution of given program, most of programmers never
take enough care to avoid potential implications caused by this fact.
Approximately 80 to 90% of signal handlers we have examined were written
in insecure manner.
This paper is an attempt to demonstrate and analyze actual risks caused by
this kind of coding practices, and to discuss threat scenarios that can be
used by an attacker in order to escalate local privileges, or, sometimes,
gain remote access to a machine. This class of vulnerabilities affects
numerous complex setuid programs (Sendmail, screen, pppd, etc.) and
several network daemons (ftpd, httpd and so on).
Thanks to Theo de Raadt for bringing this problem to my attention;
to Przemyslaw Frasunek for remote attack possibilities discussion; Dvorak,
Chris Evans and Pekka Savola for outstanding contribution to heap corruption
attacks field; Gregory Neil Shapiro and Solar Designer for their comments
on the issues discussed below. Additional thanks to Mark Loveless,
Dave Mann, Matt Power and other RAZOR team members for their support and
reviews.
Before we discuss more generalized attack scenarios, I would like to explain
signal handler races starting with very simple and clean example. We would
try to exploit non-atomic signal handler. The following code generalizes, in
simplified way, very common bad coding practice (which is present, for
example, in setuid root Sendmail program up to 8.11.3 and 8.12.0.Beta7):
/*****
void sighndlr(int dummy) {
syslog(LOG_NOTICE,user_dependent_data);
// Initial cleanup code, calling the following somewhere:
free(global_ptr2);
free(global_ptr1);
// 1 *** >> Additional clean-up code - unlink tmp files, etc <<
exit(0);
}
/**
at the beginning of main code. *
**/
signal(SIGHUP,sighndlr);
signal(SIGTERM,sighndlr);
// Other initialization routines, and global pointer
// assignment somewhere in the code (we assume that
// *** nnn is partially user-dependent, yyy does not have to be):
global_ptr1=malloc(nnn);
global_ptr2=malloc(yyy);
// 2 >> further processing, allocated memory <<
// 2 >> is filled with any data, etc... <<
This code seems to be pretty immune to any kind of security compromises. But
this is just an illusion. By delivering one of the signals handled by
sighndlr() function somewhere in the middle of main code execution (marked
as ' 2 ' in above example) code execution would reach handler function.
Let's assume we delivered SIGHUP. Syslog message is written, two pointers are
freed, and some more clean-up is done before exiting ( 1 ).
Now, by quickly delivering another signal - SIGTERM (note that already
delivered signal is masked and would be not delivered, so you cannot
deliver SIGHUP, but there is absolutely nothing against delivering SIGTERM) -
attacker might cause sighndlr() function re-entry. This is a very common
condition - 'shared' handlers are declared for SIGQUIT, SIGTERM, SIGINT,
and so on.
Now, for the purpose of this demonstration, we would like to target heap
structures by exploiting free() and syslog() behavior. It is very important
to understand how [v]syslog() implementation works. We would focus on Linux
glibc code - this function creates a temporary copy of the logged message in
so-called memory-buffer stream, which is dynamically allocated using two
malloc() calls - the first one allocates general stream description
structure, and the other one creates actual buffer, which would contain
logged message.
Please refer the following URL for vsyslog() function sources:
Stream management functions (open_memstream, etc.) can be found at:
In order for this particular attack to be successful, two conditions have
to be met:
syslog() data must be user-dependent (like in Sendmail log messages
describing transferred mail traffic),
second of these two global memory blocks must be aligned the way
that would be re-used in second open_memstream() malloc() call.
The second buffer (global_ptr2) would be free()d during the first
sighndlr() call, so if these conditions are met, the second syslog()
call would re-use this memory and overwrite this area, including
heap-management structures, with user-dependent syslog() buffer.
Of course, this situation is not limited to two global buffers - generally,
we need one out of any number of free()d buffers to be aligned that way.
Additional possibilities are related to interrupting free() chain by precise
SIGTERM delivery and/or influencing buffer sizes / heap data order by
using different input data patterns.
If so, the attacker can cause second free() pass to be called with a pointer
to user-dependent data (syslog buffer), this leads to instant root compromise
see excellent article by Chris Evans (based on observations by Pekka Savola):
Practical discussion and exploit code for the vulnerability discussed in
above article can be found there:
http://security-archive.merton.ox.ac.uk/bugtraq-200010/0084.html
Below is a sample 'vulnerable program' code:
--- vuln.c ---
#include <signal.h>
#include <syslog.h>
#include <string.h>
#include <stdlib.h>
void global1, global2;
char *what;
void sh(int dummy) {
syslog(LOG_NOTICE,"%s\n",what);
free(global2);
free(global1);
sleep(10);
exit(0);
}
int main(int argc,char* argv[]) {
what=argv[1];
global1=strdup(argv[2]);
global2=malloc(340);
signal(SIGHUP,sh);
signal(SIGTERM,sh);
sleep(10);
exit(0);
}
---- EOF ----
You can exploit it, forcing free() to be called on a memory region filled
with 0x41414141 (you can see this value in the registers at the time
of crash -- the bytes represented as 41 in hex are set by the 'A'
input characters in the variable $LOG below). Sample command lines
for a Bash shell are:
$ gcc vuln.c -o vuln
$ PAD=perl -e '{print "x"x410}'
$ LOG=perl -e '{print "A"x100}'
$ ./vuln $LOG $PAD & sleep 1; killall -HUP vuln; sleep 1; killall -TERM vuln
The result should be a segmentation fault followed by nice core dump
(for Linux glibc 2.1.9x and 2.0.7).
(gdb) back
#0 chunk_free (ar_ptr=0x4013dce0, p=0x80499a0) at malloc.c:3069
#1 0x4009b334 in libc_free (mem=0x80499a8) at malloc.c:3043
#2 0x80485b8 in sh ()
#4 0x400d5971 in __libc_nanosleep () from /lib/libc.so.6
#5 0x400d5801 in sleep (seconds=10) at ../sysdeps/unix/sysv/linux/sleep.c:85
#6 0x80485d6 in sh ()
So, as you can see, failure was caused when signal handler was re-entered.
__libf_free function was called with a parameter of 0x080499a8, which points
somewhere in the middle of our AAAs:
(gdb) x/s 0x80499a8
0x80499a8: 'A' <repeats 94 times>, "\n"
You can find 0x41414141 in the registers, as well, showing this data
is being processed. For more analysis, please refer to the paper mentioned
above.
For the description, impact and fix information on Sendmail signal
handling vulnerability, please refer to the RAZOR advisory at:
http://razor.bindview.com/publish/advisories/adv_sm8120.html
Obviously, that is just an example of this attack. Whenever signal handler
execution is non-atomic, attacks of this kind are possible by re-entering
the handler when it is in the middle of performing non-reentrant operations.
Heap damage is the most obvious vector of attack, in this case, but not the
only one.
The attack described above usually requires specific conditions
to be met, and takes advantage of non-atomic signal handler execution,
which can be easily avoided by using additional flags or blocking
signal delivery.
But, as signal can be delivered at any moment (unless explictly blocked),
this is obvious that it is possible to perform an attack without re-entering
the handler itself. It is enough to deliver a signal in a 'not appropriate'
moment. There are two attack schemes:
A) re-entering libc functions:
Every function that is not listed as reentry-safe is a potential source
of vulnerabilities. Indeed, numerous library functions are operating
on global variables, and/or modify global state in non-atomic way.
Once again, heap-management routines are probably the best example.
By delivering a signal when malloc(), free() or any other libcall of
this kind is being called, all subsequent calls to the heap management
routines made from signal handler would have unpredictable effect,
as heap state is completely unpredictable for the programmer.
Other good examples are functions working on static/global variables
and buffers like certain implementations of strtok(), inet_ntoa(),
gethostbyname() and so on. In all cases, results will be unpredictable.
B) interrupting non-atomic modifications:
This is basically the same problem, but outside library functions.
For example, the following code:
dropped_privileges = 1;
setuid(getuid());
is, technically speaking, using safe library functions only. But,
at the same time, it is possible to interrupt execution between
substitution and setuid() call, causing signal handler to be executed
with dropped_privileges flag set, but superuser privileges not dropped.
This, very often, might be a source of serious problems.
First of all, we would like to come back to Sendmail example, to
demonstrate potential consequences of re-entering libc. Note that signal
handler is NOT re-entered - signal is delivered only once:
#0 0x401705bc in chunk_free (ar_ptr=0x40212ce0, p=0x810f900) at malloc.c:3117 #1 0x4016fd12 in chunk_alloc (ar_ptr=0x40212ce0, nb=8200) at malloc.c:2601
#2 0x4016f7e6 in __libc_malloc (bytes=8192) at malloc.c:2703
#3 0x40168a27 in open_memstream (bufloc=0xbfff97bc, sizeloc=0xbfff97c0) at memstream.c:112
#4 0x401cf4fa in vsyslog (pri=6, fmt=0x80a5e03 "%s: %s", ap=0xbfff99ac) at syslog.c:142
#5 0x401cf447 in syslog (pri=6, fmt=0x80a5e03 "%s: %s") at syslog.c:102
#6 0x8055f64 in sm_syslog ()
#7 0x806793c in logsender ()
#8 0x8063902 in dropenvelope ()
#9 0x804e717 in finis ()
#10 0x804e9d8 in intsig () <---- SIGINT
#11 <signal handler called>
#12 chunk_alloc (ar_ptr=0x40212ce0, nb=4104) at malloc.c:2968
#13 0x4016f7e6 in __libc_malloc (bytes=4097) at malloc.c:2703
Heap corruption is caused by interruped malloc() call and, later, by
calling malloc() once again from vsyslog() function invoked from handler.
There are two another examples of very interesting stack corruption caused by
re-entering heap management routines in Sendmail daemon - in both cases,
signal was delivered only once:
A)
#0 0x401705bc in chunk_free (ar_ptr=0xdbdbdbdb, p=0x810b8e8) at malloc.c:3117
#1 0xdbdbdbdb in ?? ()
B)
/.../
#9 0x79f68510 in ?? ()
Cannot access memory at address 0xc483c689
We'd like to leave this one as an exercise for a reader - try to figure
out why this happens and why this problem can be exploitable. For now,
we would like to come back to our second scenario, interrupting non-atomic
code to show that targeting heap is not the only possibility.
Some programs are temporarily returning to superuser UID in cleanup
routines, e.g., in order to unlink specific files. Very often, by entering
the handler at given moment, is possible to perform all the cleanup file
access operations with superuser privileges.
Here's an example of such coding, that can be found mainly in
interactive setuid software:
--- vuln2.c ---
#include <signal.h>
#include <string.h>
#include <stdlib.h>
void sh(int dummy) {
printf("Running with uid=%d euid=%d\n",getuid(),geteuid());
}
int main(int argc,char* argv[]) {
seteuid(getuid());
setreuid(0,getuid());
signal(SIGTERM,sh);
sleep(5);
// this is a temporarily privileged code:
seteuid(0);
unlink("tmpfile");
sleep(5);
seteuid(getuid());
exit(0);
}
---- EOF ----
$ ./vuln & sleep 3; killall -TERM vuln; sleep 3; killall -TERM vuln
Running with uid=500 euid=500
Running with uid=500 euid=0
Such a coding practice can be found, par example, in 'screen' utility
developed by Oliver Laumann. One of the most obvious locations is CoreDump
handler [screen.c]:
static sigret_t
CoreDump SIGDEFARG
{
/.../
setgid(getgid());
setuid(getuid());
unlink("core");
/.../
SIGSEGV can be delivered in the middle of user-initiated screen detach
routine, for example. To better understand what and why is going on,
here's an strace output for detach (Ctrl+A, D) command:
23534 geteuid() = 0
23534 geteuid() = 0
23534 getuid() = 500
23534 setreuid(0, 500) = 0 HERE IT HAPPENS
23534 getegid() = 500
23534 chmod("/home/lcamtuf/.screen/23534.tty5.nimue", 0600) = 0
23534 utime("/home/lcamtuf/.screen/23534.tty5.nimue", NULL) = 0
23534 geteuid() = 500
23534 getuid() = 0
Marked line sets uid to zero. If SIGSEGV is delivered somewhere near this
point, CoreDump() handler would run with superuser privileges, due to
initial setuid(getuid()).
This is a very interesting issue, directly related to re-entering libc
functions and/or interrupting non-atomic code. Many complex daemons,
like ftp, some http/proxy services, MTAs, etc., have SIGURG handlers declared -
very often these handlers are pretty verbose, calling syslog(), or freeing
some resources allocated for specific connection. The trick is that SIGURG,
obviously, can be delivered over the network, using TCP/IP OOB message.
Thus, it is possible to perform attacks using network layer without
any priviledges.
Below is a SIGURG handler routine, which, with small modifications,
is shared both by BSD ftpd and WU-FTPD daemons:
static VOIDRET myoob FUNCTION((input), int input)
{
/.../
if (getline(cp, 7, stdin) == NULL) {
reply(221, "You could at least say goodbye.");
dologout(0);
}
/.../
}
As you can see in certain conditions, dologout() function is called.
This routine looks this way:
dologout(int status)
{
/.../
if (logged_in) {
delay_signaling(); / we can't allow any signals while euid==0: kinch /
(void) seteuid((uid_t) 0);
wu_logwtmp(ttyline, "", "");
}
if (logging)
syslog(LOG_INFO, "FTP session closed");
/.../
}
As you can see, the authors took an additional precaution not to allow
signal delivery in the "logged_in" case. Unfortunately, syslog() is
a perfect example of a libc function that should NOT be called during
signal handling, regardless of whether "logged_in" or any other
special condition happens to be in effect.
As mentioned before, heap management functions such as malloc() are
called within syslog(), and these functions are not atomic. The OOB
message might arrive when the heap is in virtually any possible state.
Playing with uids / privileges / internal state is an option, as well.
In most cases this is a non-issue for local attacks, as the attacker
might control the execution environment (e.g., the load average, the
number of local files that the daemon needs to access, etc.) and try
a virtually infinite number of times by invoking the same program over
and over again, increasing the possibility of delivering signal at
given point. For remote attacks, this is a major issue, but as long
as the attack itself won't cause service to stop responding, thousands of
attempts might be performed.
This is a very complex and difficult task. There are at least three aspects
of this:
Using reentrant-safe libcalls in signal handlers only. This would
require major rewrites of numerous programs. Another half-solution is
to implement a wrapper around every insecure libcall used, having
special global flag checked to avoid re-entry,
Blocking signal delivery during all non-atomic operations and/or
constructing signal handlers in the way that would not rely on
internal program state (e.g. unconditional setting of specific flag
and nothing else),
Blocking signal delivery in signal handlers.
Michal Zalewski
<lcamtuf@razor.bindview.com>
16-17 May, 2001