Thursday, June 26, 2014

Disable new user signups (registration spam) on a wordpress blog

The lovely Carol has a wordpress blog. She's been getting lots of "new user registrations," and she spends too much time deleting these new "users". These are not real people; they're more likely "spammers wanting to take control of the system through multiple user registrations" according to this site.

I did a web search on "wordpress new users" or something like this (no quotation marks) and found an article titled “How to Disable New User Registration on WordPress Using PhpMyAdmin,” which was useful, except I don't have PhpMyAdmin. And I don't have cPanel either. And that was the most promising of the links that came up. Bummer.

What's a geek to do? well, this:

$ ls
dec24.html                 wp-config-sample.php
fire-safe-box/             wp-content/
INDEX.HTML.oneclickbackup  wp-cron.php
index.php                  wp-includes/
license.txt                wp-links-opml.php
md5sums.txt                wp-load.php
readme.html                wp-login.php
wp-activate.php            wp-mail.php
wp-admin/                  wp-settings.php
wp-blog-header.php         wp-signup.php
wp-comments-post.php       wp-trackback.php
wp-config.php              xmlrpc.php
$ 
H'm, wp-signup.php you say? That looked promising. I noticed this:
 30 if ( !is_multisite() ) {
 31         wp_redirect( site_url('wp-login.php?action=register') );
 32         die();
 33 }
"wp-signup.php" [readonly]
THAT looked promising. I looked in wp-login.php and searched for "register"; I found this:
646         if ( !get_option('users_can_register') ) {
647                 wp_redirect( site_url('wp-login.php?registration=disabled') );
648                 exit();
649         }
"wp-login.php" [readonly] 921 lines
O-kay! So we want to kill off the option 'users_can_register'.

By looking around, I discovered that get_option() gets things from the cache. I searched wordpress.org for "get_option" and landed at http://wordpress.org/search/get_option, which refers to the options database table. Somehow I got the idea of looking in wp-config.php where I discovered this:

// ** MySQL settings ** //
define('DB_NAME', 'bedrockdb');    // The name of the database
define('DB_USER', 'fred_flint');     // Your MySQL username
define('DB_PASSWORD', 'yabbadabbadoo'); // ...and password
define('DB_HOST', 'hanna.bar.be.ra');    // 99% chance you won't need to change this value
(Of course the names are changed to protect… well, myself.) In another window, I typed:
$ mysql
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (111)
D'oh! I had to fill in the hostname and the username and…
$ mysql -h 'hanna.bar.be.ra' -u 'fred_flint' -p
Enter password: <here I typed “yabbadabbadoo”>
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 24209555 to server version: 5.5.37-35.1-log

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> use bedrockdb
Database changed
mysql> show tables;
+-----------------------+
| Tables_in_bedrockdb   |
+-----------------------+
| wp_commentmeta        |
| wp_comments           |
| wp_links              |
| wp_options            |
| wp_postmeta           |
| wp_posts              |
| wp_subscribe2         |
| wp_term_relationships |
| wp_term_taxonomy      |
| wp_terms              |
| wp_usermeta           |
| wp_users              |
+-----------------------+
12 rows in set (0.02 sec) 
Okay, that's good. Now which table? wp_options? Yes! And according to the above article, I want wp_options. So I said:
mysql> describe wp_options;
+--------------+---------------------+------+-----+---------+----------------+
| Field        | Type                | Null | Key | Default | Extra          |
+--------------+---------------------+------+-----+---------+----------------+
| option_id    | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| option_name  | varchar(64)         | NO   | UNI |         |                |
| option_value | longtext            | NO   |     | NULL    |                |
| autoload     | varchar(20)         | NO   |     | yes     |                |
+--------------+---------------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)

mysql> select * from wp_options where option_name = 'users_can_register'; 
+-----------+--------------------+--------------+----------+
| option_id | option_name        | option_value | autoload |
+-----------+--------------------+--------------+----------+
|         6 | users_can_register | 1            | yes      |
+-----------+--------------------+--------------+----------+
1 row in set (0.02 sec)
It's been a while since I updated a table in MySQL. Naturally I went to a web search to find a list of SQL commands. This one told me what I needed, down in "Data Commands."

Let's see, I wanted to change the option_value from 1 to 0 where… OK, I tried this.

mysql> update wp_options set option_value = 0 where option_name = 'users_can_register';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
Wow, it worked the first time! How often does that happen? Just to be sure, I typed this:
mysql> select * from wp_options where option_name = 'users_can_register';
+-----------+--------------------+--------------+----------+
| option_id | option_name        | option_value | autoload |
+-----------+--------------------+--------------+----------+
|         6 | users_can_register | 0            | yes      |
+-----------+--------------------+--------------+----------+
1 row in set (0.01 sec)

mysql> quit
Bye
Now for the acid test. I went to the sign-up page "wp-signup.php" and got: "User registration is currently not allowed."

And that's what I'll do next time.

Thursday, June 12, 2014

CSV to QIF transformation with Python3

This evening I discovered that the folks at my bank, or more probably the folks running the online banking website, have "upgraded" the user interface, and they no longer support downloading transactions to the "old" Quicken file format (i.e., "QIF"). Instead, they (mostly) support "export"ing transactions to CSV.

Fortunately, a format conversion from CSV to QIF isn't that hard. I first did a web search, which yielded

  • a website (upload your csv and we'll give you a qif file),
  • some Windows (I guess) software--free to download and try, but I guess they want money for it, and
  • maybe something else that I didn't think would work for me
so I decided to just hack something together. Of course it would be in Python, and probably python3.

I remembered that there was a module that knew how to read and write CSV files; a quick look at the library reference showed that the module is called, oddly enough, "csv"; the result is described here (produced by "pydoc3 -w csv_to_qif")

 
 
csv_to_qif
index
/mnt/home/collin/projects/csv-qif/csv_to_qif.py

transform a "csv" file on stdin to a "qif" file on stdout.

 
Modules
       
csv
os
sys

 
Functions
       
main(infile, outfile)
Read infile -- we expect valid entries to have 9 fields:
xaction#, date, descr, memo, amt debit, amt credit, bal, check#, fees
 
Write an outfile that looks like...
    !Type:Bank
    C*
    Dmm/dd/yyyy
    T<amount> (negative for withdrawal)
    N###      (## = checkNumber)
    P<descr>  (description...)
    ^
Each entry begins with "C" and ends with "^".
Here's the code. It is Copyright©2014 by me, but you are free to use, redistribute and or modify it under the terms of GPL2; see below.
#!/usr/bin/python3 -utt
# vim:et
'''transform a "csv" file on stdin to a "qif" file on stdout.'''

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 2.
# NO WARRANTY express or implied.

import csv
import os
import sys

def main(infile, outfile):
    '''Read infile -- we expect valid entries to have 9 fields:
    xaction#, date, descr, memo, amt debit, amt credit, bal, check#, fees
    
    Write an outfile that looks like...
        !Type:Bank
        C*
        Dmm/dd/yyyy
        T (negative for withdrawal)
        N###      (## = checkNumber)
        P  (description...)
        ^
    Each entry begins with "C" and ends with "^".'''

    outfile.write('!Type:Bank\n')

    reader = csv.reader(infile)
        
    for arow in reader:
        if len(arow) == 1:
            continue                    # a couple of title lines
        if len(arow) != 9:
            print("I don't grok this row with len=%d:\n\t'%s'" %
                        (len(arow), "',".join(arow)), outfile=sys.stderr)
            continue
        trans_num, date, descr, memo, deb, cred, bal, cnum, fees = arow
        if date.startswith('Date'):
            continue                    # that's the header line
        outfile.write('C*\n')
        outfile.write('D%s\n' % date)
        if deb:
            outfile.write('T%s\n' % deb)
        else:
            outfile.write('T%s\n' % cred)
        if cnum:
            outfile.write('N%s\n' % cnum)
        outfile.write('P%s\n' % descr)
        outfile.write('^\n')
    sys.exit(0)

if __name__ == '__main__':
    try:
        infile = open(sys.argv[1], 'r')
        outfile = open(sys.argv[2], 'w')
    except:
        print('usage: %s INFILE OUTFILE' % os.path.basename(sys.argv[0]))
    main(infile, outfile)
UPDATE 2014-06-24: Added a "C*" at the beginning of each entry. Without this, my quicken "check register" wasn't showing the "checks" as being Cleared.

Tuesday, May 13, 2014

Rejoice with me!

The petite woman was about to get into her car, but she was smiling and looking at me as I walked my little dog down her street. "Good morning," I said.

"Good morning!" she replied. "I was so happy…" she began, and I wondered where this was going. "… because my dog ran away, but he came back just now. I was getting ready to go to work, and there he was!" She wiggled a bit to show me how he'd greeted her.

"That's wonderful!" I said. I knew what it felt like to lose track of a pet, or a child, and to find them again.

She went on to tell me about how the dog has run away in the past, but the tag on his collar has her phone number—this time, though, she had cut his hair and left his collar off. She spoke of her futile visit to the Humane Society, and of her other dog's despondency—he had missed his buddy. "But then there he was!" she said. She could not contain her joy.

"Congratulations!" I said. "That's a great thing." We said good-bye, and I thought of a story Jesus told nearly two thousand years ago.

4“Suppose one of you has a hundred sheep and loses one of them. Does he not leave the ninety-nine in the open country and go after the lost sheep until he finds it? 5And when he finds it, he joyfully puts it on his shoulders and goes home. Then he calls his friends and neighbors together and says, 6‘Rejoice with me; I have found my lost sheep.’ 7I tell you that in the same way there will be more rejoicing in heaven over one sinner who repents than over ninety-nine righteous persons who do not need to repent.
Luke 15:4-7
The story has two points: one is that there is rejoicing in heaven when someone repents (no, really!). Our experience of joy on earth is both an echo of our Creator's joy and a hint of future joys in heaven, even as the love I feel toward my children points to the love my heavenly father feels toward me. As Merton writes,
All nature is meant to make us think of paradise. Woods, fields, valleys, hills, the rivers and the sea, the clouds traveling across the sky, light and darkness, sun and stars, remind us that the world was created as a paradise … Heaven is even now mirrored in created things.
No Man Is an Island 6.15 (p. 115)
Heaven is indeed mirrored in created things—and not just in things we can see, but also in love and joy.

The parable has a barb: The Pharisees and teachers of the law did not rejoice when "tax collectors and sinners" were listening to Jesus and presumably repenting; hence they were no friends of heaven.
The second point, and probably the reason Jesus told the parable, is this: Friends and neighbors rejoice together. That is, if someone is happy because a lost sheep or coin (or dog) is found, friends and neighbors are the ones who rejoice with them. That is, both here and in the parable of the lost coin (Luke 15:8-10), the finder calls friends and neighbors together to celebrate.

I had a chance that morning to be a neighbor to this woman, if not a friend, by rejoicing with her. Come to think of it, I'm glad we had a chance to talk, because it reminded me of the Great Shepherd's joy, which I can share in, and of the many good gifts he gives me daily.

Tuesday, April 01, 2014

I want to be this guy

I've been thinking for a while about that prayer that begins, …asking God to fill you with the knowledge of his will through all spiritual wisdom and understanding. And we pray this in order that you may live a life worthy of the Lord and may please him in every way… (Colossians 1:9-10). Last night I opened Merton's No Man Is an Island more or less at random, where I found this:
If we have the Spirit of God in our hearts, we will live by His law of charity, inclined always to peace rather than dissension, to humility rather than arrogance, to obedience rather than rebellion, to purity and temperance, to simplicity and quietness and calm, to strength, generosity, and wisdom, to prudence and all-embracing justice, and we will love others more than ourselves, for it is the commandment of Jesus that we should love one another as he has loved us (John 15:12).
Thomas Merton, No Man Is an Island 4.9 (p. 61)
Now there is a picture of the kind of guy I want to be. It's not a lot different from the description of the Spirit's fruit from Galatians 5 (love, joy, peace, patience…) but the wording is fresh (some 1900 years newer). But as with the fruit of the Spirit, this sort of life is the fruit (the result) of the Spirit; that is, it's supernatural, as Merton also explains:

None of these things can be done without prayer, and we must turn to prayer first of all, not only to discover God's will but above all to gain the grce to carry it out with all the strength of our desire.

ibid.
This put me in mind of Hebrews 4, where the author tells us that
Therefore, since we have a great high priest who has passed through the heavens, Jesus, the Son of God, let us hold fast our confession. For we do not have a high priest who cannot sympathize with our weaknesses, but one who has been tested in all things as we are, yet without sin. Therefore, let us draw near with confidence to the throne of grace, that we may receive mercy and may find grace to help in time of need.
Hebrews 4:14-16
As the old hymn says, I need thee every hour, which is a great encouragement to me to pray all the time.

Tuesday, March 25, 2014

How death helped me focus on hope in life

Uncle Harry died on February; he was about 95. On hearing that news, his brother Gilbert, nearly 97, took a nosedive and died two weeks later.

I sat with my father at Gilbert's viewing. "Every day's a gift, isn't it?" It made me think about what I hope for my final years.

Both Harry and Gilbert had had strokes; they were wheelchair-bound and had not been in good health for some years. My aunt, their younger sister, has been losing her memory for some time. If I am fortunate enough to live to 95 or 97, what do I wish for that time?

Most likely my physical condition won't be great. Even if I don't fall 10' onto a concrete floor, as Dad has, I won't be doing 20- or 30-mile bike rides at that age. And a lot of my memory will probably be gone, too; already the kids remember lots of things I have long forgotten.

David Brinkner asks, "If you could choose just one word to describe what you want people to know about you, what would it be?" What indeed? What do you or I want people to know about us—and related to that, what kind of person do you or I want to be? Putting aside the question of what remains when our memories are gone, what kind of person do I want to be?

Other than "forty years younger", what comes to mind is that I hope to finish well. I want my life to be characerized by love, joy, peace, patience, kindness, goodness and generosity, gentleness, self-control and courage. Sound like a plan? The question is, what am I doing now about it?

One important thing is to practice some of those things.

I wrote that a year ago, and since then, my brother-in-law Danny also died. He was not much older than I am today, and I wrote about him at the time. Who am I becoming? What am I doing for others? These are much more important questions than "what's my bank balance?" or "what's my raise or bonus this year?"

Here's another good one: "What am I waiting for?" We had a great sermon the other day, where our pastor pointed out that many people in the Bible had excuses, reasons they weren't "ready" for God's call: Jeremiah was too young, Abraham was too old, Moses was a poor public speaker, and so on. God uses people who aren't ready, and who fits that description better than I?

Monday, March 24, 2014

FreeBSD 8.1 issignal() passes uninitialized ksi_flags to sigqueue_add(), causing later segfault

Note: This is a long and extremely detailed account of an esoteric problem. You have been warned.

What is it like to debug a kernel panic? Well, it's a technical and intellectual challenge, and it's also emotional, with the frustration of not knowing what happened, the thrill of feeling like you've got it licked, and the disappointment of seeing the symptom reappear after your so-called "fix."

When I've done this in the past, the experience could be shared only with my fellow employees, because typically the problem lay in proprietary software. On a recent occasion, though, I found a problem in FreeBSD 8.1-RELEASE; its source code is public, so at last I can share one of these with a wider audience.

My colleagues and I were running a program, maytag.Rd, under gdb, on FreeBSD virtual machines (VMs), and the kernel was panicking frequently. This meant that all programs running in that VM would stop working, and the VM would then have to be restarted. This was particularly annoying when it happened after a long sequence of operations to get the maytag into a certain state, because the maytag couldn't be resumed; all the setup operations needed repeating.

There was nothing special about the maytag; it didn't run as superuser or anything shady like that, so the frequent crashes were puzzling. It did use SIGALRM, but that shouldn't be able to crash the kernel. I kept hitting the panic, and asked my colleagues about their experience; yes, they also hit it frequently. Rather than continuing to put up with it, I decided to take a run at solving it.

The first step in debugging this problem was getting a dump of main memory. Our excellent support staff created a VM instance for me with enough disk-backed "swap" to dump primary memory, or "core," and I ran the program on that VM, under gdb. After a short while, I was rewarded with a coredump. I then ran the kernel debugger, kgdb:

NOTE: Material in the green boxes is taken verbatim from notes/updates in the software defect report.

% kgdb /boot/kernel/kernel.symbols /var/crash/vmcore.0 
GNU gdb 6.1.1 [FreeBSD]
Copyright 2004 Free Software Foundation, Inc. 
(…FSF license info elided…)

Unread portion of the kernel message buffer:


Fatal trap 12: page fault while in kernel mode
cpuid = 0; apic id = 00
fault virtual address = 0x1e
fault code  = supervisor read data, page not present
instruction pointer = 0x20:0xffffffff805a83cf
stack pointer         = 0x28:0xffffff8077025af0
frame pointer         = 0x28:0xffffff8077025b40
code segment  = base 0x0, limit 0xfffff, type 0x1b
   = DPL 0, pres 1, long 1, def32 0, gran 1
processor eflags = interrupt enabled, resume, IOPL = 0
current process  = 1418 (initial thread)
trap number  = 12
panic: page fault
cpuid = 0
Uptime: 37m21s
Physical memory: 4081 MB
Dumping 1639 MB: 1624 1608 1592 1576 1560 1544 …you get the idea… 88 72 56 40 24 8

Reading symbols from /boot/modules/vmxnet.ko...done.
Loaded symbols for /boot/modules/vmxnet.ko
Reading symbols from /boot/modules/vmmemctl.ko...done.
Loaded symbols for /boot/modules/vmmemctl.ko
#0  doadump () at pcpu.h:223
223 pcpu.h: No such file or directory.
 in pcpu.h
(kgdb) bt
#0  doadump () at pcpu.h:223
#1  0xffffffff805a4d29 in boot (howto=260)
    at /usr/src/sys/kern/kern_shutdown.c:416
#2  0xffffffff805a515c in panic (fmt=0xffffffff80964764 "%s")
    at /usr/src/sys/kern/kern_shutdown.c:590
#3  0xffffffff8088b928 in trap_fatal (frame=0xffffff0001e00000,
                                      eva=Variable "eva" is not available.)
    at /usr/src/sys/amd64/amd64/trap.c:777
#4  0xffffffff8088bcf4 in trap_pfault (frame=0xffffff8077025a40, usermode=0)
    at /usr/src/sys/amd64/amd64/trap.c:693
#5  0xffffffff8088c53a in trap (frame=0xffffff8077025a40)
    at /usr/src/sys/amd64/amd64/trap.c:451
#6  0xffffffff80871f03 in calltrap ()
    at /usr/src/sys/amd64/amd64/exception.S:223
#7  0xffffffff805a83cf in sigqueue_get (sq=0xffffff0001e00080, signo=14, 
    si=0xffffff8077025b70) at /usr/src/sys/kern/kern_sig.c:295
#8  0xffffffff805a855f in postsig (sig=14) at /usr/src/sys/kern/kern_sig.c:2717
#9  0xffffffff805e2777 in ast (framep=0xffffff8077025c80)
    at /usr/src/sys/kern/subr_trap.c:234
#10 0xffffffff80872ec9 in doreti_ast ()
    at /usr/src/sys/amd64/amd64/exception.S:633
#11 0x0000000000000002 in ?? ()
…
(Frame 11 is certainly wrong, so I elided everything after it.)

I felt pretty happy about this. I caught the system in the act of doing something really stupid. Now if I could figure out how we got to this silly place, there would be…well, if not fame and fortune exactly, at least some panic-free runs for my colleagues and me.

(bt is the gdb command to do a "backtrace". Here's what I mean by that: when some code calls a routine, the called routine usually writes a "stack frame" into memory, which has information about where the caller was, parameters, etc. A backtrace is the output that comes from tracing back through those frames. In other words, find the routine that was running, and see what routine called it, then see what routine called that, and so on. The convention in gdb is that the current frame is numbered zero; its caller is described in frame 1, and so on.)

We hit the trap while in frame 7, frame 6 being the trap marker. By the way, everybody was hitting the same trap ("Fatal trap 12: page fault while in kernel mode") at the same place (instruction pointer = 0x20:0xffffffff805a83cf). It was nice to know that I'd hit the same panic on the machine with the modified configuration. What's happening in that instruction?

0xffffffff805a83b5 <sigqueue_get+85>: mov    0x20(%r15),%rsi
0xffffffff805a83b9 <sigqueue_get+89>: test   %rsi,%rsi
0xffffffff805a83bc <sigqueue_get+92>: jne    0xffffffff805a83cf <sigqueue_get+111>
0xffffffff805a83be <sigqueue_get+94>: jmpq   0xffffffff805a848a <sigqueue_get+298>
0xffffffff805a83c3 <sigqueue_get+99>: test   %r12,%r12
0xffffffff805a83c6 <sigqueue_get+102>: mov    %r12,%rsi
0xffffffff805a83c9 <sigqueue_get+105>: je     0xffffffff805a848a <sigqueue_get+298>
0xffffffff805a83cf <sigqueue_get+111>: cmp    %r13d,0x10(%rsi)
0xffffffff805a83d3 <sigqueue_get+115>: mov    (%rsi),%rdx
0xffffffff805a83d6 <sigqueue_get+118>: mov    %rdx,%r12
0xffffffff805a83d9 <sigqueue_get+121>: jne    0xffffffff805a83c3 <sigqueue_get+99>
0xffffffff805a83db <sigqueue_get+123>: test   %r14d,%r14d
0xffffffff805a83de <sigqueue_get+126>: jne    0xffffffff805a8466 <sigqueue_get+262>
…
(kgdb) info reg
rax            0x0 0
rbx            0xffffff8077025b70 -547759170704
rcx            0xffffff0001e72860 -1099479701408
rdx            0xe 14
rsi            0xe 14
rdi            0xffffff00018542a0 -1099486117216
rbp            0xffffff8077025b40 0xffffff8077025b40
rsp            0xffffff8077025b00 0xffffff8077025b00
r8             0x0 0
r9             0x2000 8192
r10            0x0 0
r11            0x0 0
r12            0xe 14
r13            0xe 14
r14            0x1 1
r15            0xffffff0001e00080 -1099480170368
rip            0xffffffff805a83cf 0xffffffff805a83cf <sigqueue_get+111>
(Note: To get the list of assembly instructions above, I typed "disass sigqueue_get"; "info reg" means "show me the registers." Assembly instructions can be decrypted by looking at a document downloadable from Intel. For those who aren't sure about the direction of Intel instructions, gdb seems to print them with "left-to-right" semantics—e.g., "mov %r12,%rsi" means "copy the contents of %r12 into %rsi.")

So we seem to be comparing the contents of %r13d vs. the memory located at (%rsi+0x10), highlighted like this; since %rsi has 0xe, we'd expect the faulting address to be 0x1e, as indeed it is (search backward for "fault virtual address"). That's the smoking gun, and it's always nice to see that the machine is consistent with itself, even in crashes. That was progress, but not nearly enough. What are we doing there? The backtrace says we're at line 295:

(kgdb) f 7
#7  0xffffffff805a83cf in sigqueue_get (sq=0xffffff0001e00080, signo=14, 
    si=0xffffff8077025b70) at /usr/src/sys/kern/kern_sig.c:295
295  TAILQ_FOREACH_SAFE(ksi, &sq->sq_list, ksi_link, next) {
(kgdb) ptype sq->sq_list
type = struct {
    struct ksiginfo *tqh_first;
    struct ksiginfo **tqh_last;
}
(kgdb) x/2xg &sq->sq_list
0xffffff0001e000a0: 0xffffff8077025b80 0xffffff8077025b80
I thought this was weird, since tqh_last is supposed to be pointer-to-pointer. (Actually, my intuition was completely wrong about that; in this particular queue, it's perfectly OK for them to have the same value.) More to the point, though, this piece of memory doesn't look like a struct ksiginfo:
(kgdb) x/16xg 0xffffff8077025b80
0xffffff8077025b80: 0x000000000000000e 0x0000000000010006
0xffffff8077025b90: 0x0000000000000000 0x0000000000000000
0xffffff8077025ba0: 0x0000000000000000 0x0000000000000000
0xffffff8077025bb0: 0x0000000000000000 0x0000000000000000
0xffffff8077025bc0: 0x0000000000000000 0x0000000000000000
0xffffff8077025bd0: 0x0000000000000000 0x0000000000000000
0xffffff8077025be0: 0x0000000000000010 0x0000000000000246
0xffffff8077025bf0: 0x0000000000002000 0x0000000000000000
(kgdb) p *sq->sq_list.tqh_first
$8 = {ksi_link = {tqe_next = 0xe, tqe_prev = 0x10006}, ksi_info = {
    si_signo = 0, si_errno = 0, si_code = 0, si_pid = 0, si_uid = 0, 
    si_status = 0, si_addr = 0x0, si_value = {sival_int = 0, sival_ptr = 0x0, 
      sigval_int = 0, sigval_ptr = 0x0}, _reason = {_fault = {_trapno = 0}, 
      _timer = {_timerid = 0, _overrun = 0}, _mesgq = {_mqd = 0}, _poll = {
        _band = 0}, __spare__ = {__spare1__ = 0, __spare2__ = {0, 0, 0, 0, 0, 
          0, 0}}}}, ksi_flags = 16, ksi_sigq = 0x246}
(kgdb) 
When I saw this, I was pretty unhappy. It looked like the whole structure was trashed, not just one part of it. How was I going to figure this out?

Clearly 0xffffff8077025b80 was a bad pointer, or perhaps memory there got corrupted. I needed to see how we got into this mess.

(kgdb) p count
$10 = 1
(kgdb) l sigqueue_get
278 static int
279 sigqueue_get(sigqueue_t *sq, int signo, ksiginfo_t *si)
280 {
281  struct proc *p = sq->sq_proc;
282  struct ksiginfo *ksi, *next;
283  int count = 0;
284 
…
290  if (SIGISMEMBER(sq->sq_kill, signo)) {
291   count++;
292   SIGDELSET(sq->sq_kill, signo);
293  }
294 
295  TAILQ_FOREACH_SAFE(ksi, &sq->sq_list, ksi_link, next) {
296   if (ksi->ksi_signo == signo) {
297    if (count == 0) {
298     TAILQ_REMOVE(&sq->sq_list, ksi, ksi_link);
299     ksi->ksi_sigq = NULL;
300     ksiginfo_copy(ksi, si);
301     if (ksiginfo_tryfree(ksi) && p != NULL)
302      p->p_pendingcnt--;
303    }
304    if (++count > 1)
305     break;
306   }
307  }
count=1 (assuming gdb is telling the truth) says that either:
1. we ran 291; or
2. we ran 297-303 once, removing an element from the queue;
    then we bumped count in 304.  That's how we panicked 
    2nd time around in 295.

I reject #1 because the queue is plainly not what it was when we entered.
Why did I think I knew this? Because if it had been, we would have panicked the first time through, or so I thought. Was I right? I suppose so; some hours later I was sure that we executed line 300.
Now when we entered, we had si==0xffffff8077025b70 I guess.
At that time we would have assigned ksi to... umm, that value?
(kgdb) p *si
$16 = {ksi_link = {tqe_next = 0x0, tqe_prev = 0x0}, ksi_info = {si_signo = 14, 
    si_errno = 0, si_code = 65542, si_pid = 0, si_uid = 0, si_status = 0, 
    si_addr = 0x0, si_value = {sival_int = 0, sival_ptr = 0x0, sigval_int = 0, 
      sigval_ptr = 0x0}, _reason = {_fault = {_trapno = 0}, _timer = {
        _timerid = 0, _overrun = 0}, _mesgq = {_mqd = 0}, _poll = {_band = 0}, 
      __spare__ = {__spare1__ = 0, __spare2__ = {0, 0, 0, 0, 0, 0, 0}}}}, 
  ksi_flags = 0, ksi_sigq = 0x0}

Now THAT looks completely reasonable.

But somehow we ended up like this??
(kgdb) p sq->sq_list
$15 = {tqh_first = 0xffffff8077025b80, tqh_last = 0xffffff8077025b80}
At this point I was completely stumped. I hadn't yet noticed that 0xffffff8077025b80 was exactly si+0x10 (i.e., a location inside *si), so I concocted a bunch of theories about how si->ksi_link.tqe_next might have been 0xffffff8077025b80, etc.

The above came from a report filed at 12:46pm Monday; within eleven minutes, I had looked at the caller, in frame 8 of the backtrace.

(kgdb) up
#8  0xffffffff805a855f in postsig (sig=14) at /usr/src/sys/kern/kern_sig.c:2717
2717  if (sigqueue_get(&td->td_sigqueue, sig, &ksi) == 0 &&

2700 int
2701 postsig(sig)
2702     register int sig;
2703 {
2704     struct thread *td = curthread;
2705     register struct proc *p = td->td_proc;
2706     struct sigacts *ps;
2707     sig_t action;
2708     ksiginfo_t ksi;
2709     sigset_t returnmask, mask;
2710 
2711     KASSERT(sig != 0, ("postsig"));
2712 
2713     PROC_LOCK_ASSERT(p, MA_OWNED);
2714     ps = p->p_sigacts;
2715     mtx_assert(&ps->ps_mtx, MA_OWNED);
2716     ksiginfo_init(&ksi);
2717     if (sigqueue_get(&td->td_sigqueue, sig, &ksi) == 0 &&
2718         sigqueue_get(&p->p_sigqueue, sig, &ksi) == 0)
2719         return (0);
"/usr/src/sys/kern/kern_sig.c" [readonly]
The thing I observed here was that ksi was on-stack. It was, in fact, at the address noted earlier (0xffffff8077025b70). Somebody must have added an on-stack element some time earlier, I thought. But I was at a loss as to how that could have happened.

It would be another two hours before I noticed this; at 2:56pm I updated the report:

 340 static int
 341 sigqueue_add(sigqueue_t *sq, int signo, ksiginfo_t *si)
 342 {
…
 354     /* directly insert the ksi, don't copy it */
 355     if (si->ksi_flags & KSI_INS) {
 356         if (si->ksi_flags & KSI_HEAD)
 357             TAILQ_INSERT_HEAD(&sq->sq_list, si, ksi_link);
 358         else
 359             TAILQ_INSERT_TAIL(&sq->sq_list, si, ksi_link);
 360         si->ksi_sigq = sq;
 361         goto out_set_bit;
 362     }
The key thing here is that we would insert the element only if KSI_INS is set in ksi_flags. I suspected at this time that KSI_INS might have been set erroneously. Quoting from the report:
sigqueue_add() is called by tdsignal() and issignal().

Sure enough, issignal() has an on-stack ksi whose flags are not initialized.

2490 static int
2491 issignal(struct thread *td, int stop_allowed)
2492 {
2493         struct proc *p;
2494         struct sigacts *ps;
2495         struct sigqueue *queue;
2496         sigset_t sigpending;
2497         ksiginfo_t ksi;
...
2570                                 if (ksi.ksi_signo != 0) {
2571                                         ksi.ksi_flags |= KSI_HEAD;
2572                                         if (sigqueue_add(&td->td_sigqueue, sig,
2573                                             &ksi) != 0)
2574                                                 ksi.ksi_signo = 0;
2575                                 }
2576                                 if (ksi.ksi_signo == 0)
2577                                         sigqueue_add(&td->td_sigqueue, sig,
2578                                             NULL);
Nothing modifies ksi.ksi_flags between 2497 and 2570. That's an invitation to disaster, because whatever random value happens to be in ksi_flags is the value that will be interpreted by the callee. In particular, though line 2570 sets the KSI_HEAD bit, we have no idea whether any other bit is set.

Half an hour later, I decided that was wrong—at 3:25pm I posted this:

NEVERMIND --- if we get to 2572 it means ksi_signo is nonzero,
which means we must have done 2541 and maybe 2543:

2540             ksi.ksi_signo = 0;
2541             if (sigqueue_get(queue, sig, &ksi) == 0) {
2542                 queue = &p->p_sigqueue;
2543                 sigqueue_get(queue, sig, &ksi);
2544             } 

sigqueue_get() always zaps the flags (killing KSI_INS) if setting
ksi_signo.

static __inline void
ksiginfo_copy(ksiginfo_t *src, ksiginfo_t *dst)
{
        (dst)->ksi_info = src->ksi_info;
        (dst)->ksi_flags = (src->ksi_flags & KSI_COPYMASK);
} 
Another 90 minutes later, about 5:00pm, I decided I was wrong yet again:
Dang, that's not quite true.  sigqueue_get() can update ksi_signo
without updating ksi_flags.

I think if one does a sigqueue_add(sq, SIGALRM, NULL);
then we'll SIGADDSET(sq->sq_kill, SIGALRM) then 
SIGADDSET(sq->sq_signals, SIGALRM) then return 0. 
Much as I'd like to tell you that the process is orderly and methodical, proceeding from what's known to what's unknown through a sequence of logical steps, the truth is that it usually starts with some observations, followed by some hypotheses. The hypotheses are investigated, but at various points, important details go unnoticed; tentative conclusions are then reversed, and so on. So it was in this case.

My 5pm update continued:

Now the maytag.Rd process is traced, which means we could enter this part of
issignal():

2505         int traced = (p->p_flag & P_TRACED) || (p->p_stops & S_SIG);
...
2532         if (p->p_flag & P_TRACED && (p->p_flag & P_PPWAIT) == 0) {
2533             /*
2534              * If traced, always stop.
2535              * Remove old signal from queue before the stop.
2536              * XXX shrug off debugger, it causes siginfo to
2537              * be thrown away.
2538              */
2539             queue = &td->td_sigqueue;
2540             ksi.ksi_signo = 0;
2541             if (sigqueue_get(queue, sig, &ksi) == 0) {
2542                 queue = &p->p_sigqueue;
2543                 sigqueue_get(queue, sig, &ksi);
2544             }
...
2565                 sigqueue_add(queue, sig, NULL);
2566                 if (SIGISMEMBER(td->td_sigmask, sig))
2567                     continue;
2568                 signotify(td);

The sigqueue_get() might not find anything in
the queue, but just find the bit set.

(kgdb) p/x p->p_flag
$2 = 0x10004882
566 #define P_CONTROLT  0x00002 /* Has a controlling terminal. */
572 #define P_HADTHREADS    0x00080 /* Has had threads (no cleanup shortcuts) */
576 #define P_TRACED    0x00800 /* Debugged process being traced. */
579 #define P_EXEC      0x04000 /* Process called exec. */
593 #define P_INMEM     0x10000000 /* Loaded into memory. */
What that last part means is that sure enough, P_TRACED is set but P_PPWAIT is clear, so we likely did enter the block starting at 2539.

At this point, i.e., at 5pm Monday, I had only a rough idea of how everything happened, but I felt quite sure that the bad code in issignal() (that is, random bits in ksi->ksi_flags) had something to do with it. It was easy enough to create a kernel modified as follows:

 2540             ksi.ksi_signo = 0;
 2540             ksiginfo_init(&ksi);
I ran some experiments and proved that this change made the symptom go away. So here's where I was at that point:
  • I'd found some code that was definitely bad;
  • I had a plausible explanation, albeit an incomplete one, for how that bad code might have resulted in an on-stack ksiginfo_t to be added to the current thread's queue (and thus caused the panic);
  • I had devised a fix and tried it. The symptom, which I formerly could reproduce at will, disappeared convincingly after the aforementioned bad code was corrected.
In other words, I was done.

Looking at all of this today, though, over 1½ years later, I was somewhat more anxious to work out a more complete explanation of the original problem. Here's today's shot at it: The problem happens because issignal() erroneously adds an on-stack structure to a thread's td_sigqueue, then returns. Shortly afterward, postsig() is called, which called sigqueue_get(); the latter routine copied one siginfo_t into postsig()'s local variable, corrupting the bogus siginfo_t enqueued by issignal(). In particular, ksi_link.tqe_next was corrupted, causing a queue-scanning loop to dereference an invalid pointer and panicking the box. Specifically:

  1. ast() calls cursig() (which in turn calls issignal()), then postsig(), in rapid succession:
        229         if (flags & TDF_NEEDSIGCHK || p->p_pendingcnt > 0 ||
        230             !SIGISEMPTY(p->p_siglist)) {
        231                 PROC_LOCK(p);
        232                 mtx_lock(&p->p_sigacts->ps_mtx);
        233                 while ((sig = cursig(td, SIG_STOP_ALLOWED)) != 0)
        234                         postsig(sig);
        235                 mtx_unlock(&p->p_sigacts->ps_mtx);
        236                 PROC_UNLOCK(p);
        237         }
  2. issignal() calls sigqueue_get(), passing its ksi struct with its uninitialized (random) flags. The KSI_INS flag happens to be unintentionally set to 1.

    Now, sigqueue_get() may set si->ksi_signo (parameter si, i.e., issignal()'s local variable ksi), without touching anything else in the struct—that is, leaving ksi.ksi_flags with random bits set.

    Which signal was it? I'm not sure. I was going to say something other than SIGALRM (maybe SIGTRAP?) but I didn't look that far because I was hot on the trail—or so I hoped!

  3. The trap (sorry) is now set: we have ksi.ksi_signo nonzero. By the way, I believe sq->sq_list had one other element on it, an element whose address we no longer have. Probably that element was the SIGALRM (14 or 0xe).

    Now we execute 2570-2573 in issignal():

       2550                         if (sig != newsig) {
    ...
       2569                         } else {
       2570                                 if (ksi.ksi_signo != 0) {
       2571                                         ksi.ksi_flags |= KSI_HEAD;
       2572                                         if (sigqueue_add(&td->td_sigqueue, sig,
       2573                                             &ksi) != 0)
       2574                                                 ksi.ksi_signo = 0;
       2575                                 }
    Now sigqueue_add() will oblige us by... well, you can see it:
        340 static int
        341 sigqueue_add(sigqueue_t *sq, int signo, ksiginfo_t *si)
        342 {
    ...
        354         /* directly insert the ksi, don't copy it */
        355         if (si->ksi_flags & KSI_INS) {
        356                 if (si->ksi_flags & KSI_HEAD)
        357                         TAILQ_INSERT_HEAD(&sq->sq_list, si, ksi_link);
        358                 else
        359                         TAILQ_INSERT_TAIL(&sq->sq_list, si, ksi_link);
        360                 si->ksi_sigq = sq;
    In particular, ksi (the local variable of issignal()) could very well have been at 0xffffff8077025b80.
  4. As noted in step 1 above, ast() soon calls postsig(14); which in turn calls sigqueue_get(&td->td_sigqueue, sig, &ksi), where ksi was located at 0xffffff8077025b70. sigqueue_get() I guess did the following:
    1. examined sq->sq_kill (line 290) and concluded we didn't have a match; thus skipped 291;
    2. at 295–296, found the first element, which matched;
    3. with count==0, executed 298 to remove this element from the list, and crucially 300, copying the contents of the mystery element into the struct pointed to by (parameter) si; in doing this, we overwrote whatever had been at 0xffffff8077025b80
        290         if (SIGISMEMBER(sq->sq_kill, signo)) {
        291                 count++;
        292                 SIGDELSET(sq->sq_kill, signo);
        293         }
        294  
        295         TAILQ_FOREACH_SAFE(ksi, &sq->sq_list, ksi_link, next) {
        296                 if (ksi->ksi_signo == signo) {
        297                         if (count == 0) {
        298                                 TAILQ_REMOVE(&sq->sq_list, ksi, ksi_link);
        299                                 ksi->ksi_sigq = NULL;
        300                                 ksiginfo_copy(ksi, si);
        301                                 if (ksiginfo_tryfree(ksi) && p != NULL)
        302                                         p->p_pendingcnt--;
        303                         }
        304                         if (++count > 1)
        305                                 break;
        306                 }
        307         }
  5. We continued to 304 and incremented count, then looped around to returned to 295 in sigqueue_get().
  6. Still in sigqueue_get(), we went to the next element in the queue (which we knew from before was 0xffffff8077025b80) and repeated the test of line 296. This failed because si_signo didn't match:
    $8 = {ksi_link = {tqe_next = 0xe, tqe_prev = 0x10006}, ksi_info = {
            si_signo = 0, si_errno = 0, si_code = 0, si_pid = 0, si_uid = 0, 
            si_status = 0, si_addr = 0x0, si_value = {sival_int = 0, sival_ptr = 0x0, 
              sigval_int = 0, sigval_ptr = 0x0}, _reason = {_fault = {_trapno = 0}, 
              _timer = {_timerid = 0, _overrun = 0}, _mesgq = {_mqd = 0}, _poll = {
                _band = 0}, __spare__ = {__spare1__ = 0, __spare2__ = {0, 0, 0, 0, 0, 
                  0, 0}}}}, ksi_flags = 16, ksi_sigq = 0x246}
    so we went to the next element in the queue.
  7. But of course, ksi_link.tqe_next has been corrupted, and so when we try to load ksi->ksi_signo, we get the segfault.
One thing remains unexplained: In issignal(), we have to execute 2572–2573 I think, so that sigqueue_add() gets called with the address of (local variable) ksi. However, to get there, we must have executed 2571, which would force the new element (the one I think was incorrectly inserted) to be inserted at the head of the queue (sigqueue_add() lines 356–357 above).

Perhaps the bad element was inserted some time ago, and we're only now corrupting the queue? Or we did two of them, one after another, with the good one... second? I'm not quite sure, but I think we've gotten a little further than I had in 2012. Has this been debugged enough? Well, in one sense it was debugged enough in 2012: I saw bad code, I had a plausible if incomplete explanation of how it could have caused the symptom, and so on. If I had to stand in front of a classroom and explain that this is absolutely what happened, I couldn't give a complete explanation.

We could go further—for example, we could look at the stack frame sizes and local variable layouts for issignal() and sigqueue_get() to see if they're consistent with the idea that ksi in one routine could be 0x10 bytes from ksi in the other. But I think you've got the idea, and this post is too long already.

Sunday, March 16, 2014

Saved—from what?

Evangelicals—at least evangelicals in the ’60s and ’70s—were fond of asking, "Are you saved?" They would probably be perplexed at this 2010 comment from Barbara Brown Taylor: “The problem is, many of the people in need of saving are in churches, and at least part of what they need saving from is the idea that God sees the world the same way they do” (from An Altar in the World: A Geography of Faith).

I saw this quote a year or two ago on the big screen at church. It was provocative then, and I remembered it recently when thinking about Colossians 2, which says that man-made rules—“Do not handle! Do not taste! Do not touch!” (2:20)—lack any value in restraining sensual indulgence (2:23). The passage brought up for me the question of what it means to be saved. Why? Because it suggests that we at least need saving from sensual indulgence. And Taylor's remark makes me wonder, what should we talk about, when we talk about being saved?

The Apostle Paul writes that we were once "foolish, disobedient, led astray, slaves to various passions and pleasures, spending our days in malice and envy…" (Titus 3) but that God saved us by his goodness and mercy. He also writes that God has saved us from the power of darkness (Colossians 1:13) and brought us into the kingdom of his dear son. In 2 Timothy 1, Ephesians 2 and elsewhere, Paul writes again and again of salvation as an accomplished fact.

But Paul also writes that Jesus saves us from the wrath to come (1 Thessalonians 1:10); he says that God "will rescue me from this body of death" (Romans 7:24–25), and that we eagerly await the redemption of our bodies (8:23–25). All this is about the future. Indeed, the Lord Jesus Christ himself said, "He who stands firm to the end will be saved." (Matthew 24). And in 2 Corinthians 3:18, we read that we are being transformed into his (Christ's) likeness with ever-inceasing glory, which is something that continues throughout our lives here on earth. Which brings me to Colossians 1:9-12:

9For this reason, since the day we heard about you, we have not stopped praying for you and asking God to fill you with the knowledge of his will through all spiritual wisdom and understanding. 10And we pray this in order that you may live a life worthy of the Lord and may please him in every way: bearing fruit in every good work, growing in the knowledge of God, 11being strengthened with all power according to his glorious might so that you may have great endurance and patience, and joyfully 12giving thanks to the Father, who has qualified you to share in the inheritance of the saints in the kingdom of light.
Colossians 1:9-12 NIV
Now there's a picture of a good life: fruitful in good works, knowing God more and more, and so on. Something I notice about this passage is that Paul doesn't simply say: "Live this way"; rather, he prays for God to do something to make the God-pleasing life even possible.

What that tells me is that this kind of life isn't just difficult; without help from God it's impossible. Put differently, we need to be saved from the life of ignorance of God's will, the life of folly and fruitlessness, of distraction and impatience—the joyless life of the spoiled and entitled child.

Besides all that, I also need to be saved from my foolish impulses and tendencies. It is true that the fruit of the Spirit (Galatians 5) includes "self-control" (other versions say "sober-mindedness" and the like), but the promise around temptation is that God will provide a way of escape (1 Corinthians 10:13). This suggests that our self-control is limited, that willpower is overrated.

But I need more than just a way of escape when tempted, more than just protection from doing something stupid. What I need is to be transformed, to be changed. I want to be the kind of person who isn't tempted in the first place. I want my mind and heart to be filled with the knowledge of God's will so that I naturally give my heart to heavenly things (Colossians 3:1–2).

Returning to Colossians 2:23 and the need for restraining sensual indulgence, I think Paul is telling us that sensual indulgence isn't just some activity we do or don't do; rather, it's a force that, if unrestrained, will halt our spiritual growth so that we remain children permanently, infants tossed back and forth by the waves, and blown here and there by every wind of teaching and by the cunning and craftiness of men in their deceitful scheming (Ephesians 4:14). We need to be saved from these waves and winds.

And do we sit by idly while being saved from these things? A better idea would be to follow Paul's example and press on toward the goal to win the prize for which God has called me heavenward (Philippians 3:14). And, as the author of Hebrews tells us, to

…throw off everything that hinders and the sin that so easily entangles, and…run with perseverance the race marked out for us. 2Let us fix our eyes on Jesus, the author and perfecter of our faith, who for the joy set before him endured the cross, scorning its shame, and sat down at the right hand of the throne of God. 3Consider him who endured such opposition from sinful men, so that you will not grow weary and lose heart.
from Hebrews 12:1-3
Paul gives us a terrific encouragement along these lines, from Philippians: “[W]ork out your salvation with fear and trembling, for it is God who works in you to will and to act according to his good purpose” (from Philippians 2:12–13). In other words, as we press on toward the goal, as we throw off everything that hinders and run with perseverance, God works in us to want to keep going, as he also saves us from the useless life we might otherwise live.