Re: Cancel key now ready - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Cancel key now ready
Date
Msg-id 11137.897332138@sss.pgh.pa.us
Whole thread Raw
In response to Cancel key now ready  (Bruce Momjian <maillist@candle.pha.pa.us>)
Responses Re: [HACKERS] Re: Cancel key now ready
List pgsql-hackers
Bruce Momjian <maillist@candle.pha.pa.us> writes:
> I have added code to the postmaster to generate a random cancel key by
> calling gettimeofday() on postmaster startup and on the first invocation
> of a backend, and merged the micro-seconds of the two times to seed the
> random number generator.

There were several things I didn't like about Bruce's first cut.
His method for generating a random seed in the postmaster is good,
but there are several security holes elsewhere.

> Not sure if you can
> predict forward, but it is probably impossible to predict randoms
> backward on any of our supported platforms.

Actually, it's not that hard.  Nearly all implementations of random()
are basically just
    seed = (seed * a + b) % c;
    return seed;
for some constants a,b,c --- which a potential attacker could easily
find out.  So if the attacker can execute random() in a backend before
it gets used for anything else, he can back up to the last random value
generated in the postmaster.  The most secure way to prevent this is to
re-execute srandom() during the startup of each backend, so that it will
have a random() sequence that's unrelated to the postmaster's.

Also, Bruce was assuming that the random_seed value wouldn't be visible
in a backend ... but the forked-off backend will have a copy just
sitting there, readily accessible if you can figure out its address.
Backend startup should zero out this variable to be on the safe side.

Attached is a patch that fixes these leaks, and does a couple other
things as well:
  * Computes and saves a cancel key for each backend.
  * fflush before forking, to eliminate double-buffering problems
    between postmaster and backends.
  * Go back to two random() calls instead of one to generate random
    salt.  I'm not sure why Bruce changed that, but it looks much
    less secure to me; the one-call way is exposing twice as many
    bits of the current random seed.
  * Fix "ramdom" typo.

Next is to transmit the PID + cancel key to the frontend and modify
libpq's cancel routine to send it back.  I'll work on that later.

            regards, tom lane


*** postmaster.c~    Mon Jun  8 14:10:39 1998
--- postmaster.c    Mon Jun  8 14:44:15 1998
***************
*** 113,118 ****
--- 113,119 ----
  typedef struct bkend
  {
      int            pid;            /* process id of backend */
+     long        cancel_key;        /* cancel key for cancels for this backend */
  } Backend;

  /* list of active backends.  For garbage collection only now. */
***************
*** 198,204 ****
--- 199,212 ----
  static    int            orgsigmask = sigblock(0);
  #endif

+ /*
+  * State for assigning random salts and cancel keys.
+  * Also, the global MyCancelKey passes the cancel key assigned to a given
+  * backend from the postmaster to that backend (via fork).
+  */
+
  static unsigned int random_seed = 0;
+ long MyCancelKey = 0;

  extern char *optarg;
  extern int    optind,
***************
*** 602,618 ****
              return (STATUS_ERROR);
          }

!         if (random_seed == 0)
          {
              gettimeofday(&later, &tz);

              /*
               *    We are not sure how much precision is in tv_usec, so we
!              *    swap the nibbles of 'later' and XOR them with 'now'
               */
              random_seed = now.tv_usec ^
                      ((later.tv_usec << 16) |
!                     ((unsigned int)(later.tv_usec & 0xffff0000) >> 16));
          }

          /*
--- 610,631 ----
              return (STATUS_ERROR);
          }

!         /*
!          * Select a random seed at the time of first receiving a request.
!          */
!         while (random_seed == 0)
          {
              gettimeofday(&later, &tz);

              /*
               *    We are not sure how much precision is in tv_usec, so we
!              *    swap the nibbles of 'later' and XOR them with 'now'.
!              *  On the off chance that the result is 0, we loop until
!              *  it isn't.
               */
              random_seed = now.tv_usec ^
                      ((later.tv_usec << 16) |
!                     ((later.tv_usec >> 16) & 0xffff));
          }

          /*
***************
*** 1075,1080 ****
--- 1088,1101 ----
      }
  #endif

+     /*
+      * Compute the cancel key that will be assigned to this backend.
+      * The backend will have its own copy in the forked-off process'
+      * value of MyCancelKey, so that it can transmit the key to the
+      * frontend.
+      */
+     MyCancelKey = PostmasterRandom();
+
      if (DebugLvl > 2)
      {
          char      **p;
***************
*** 1088,1104 ****
          fprintf(stderr, "-----------------------------------------\n");
      }

      if ((pid = fork()) == 0)
      {  /* child */
          if (DoBackend(port))
          {
              fprintf(stderr, "%s child[%d]: BackendStartup: backend startup failed\n",
!                     progname, pid);
!             /* use _exit to keep from double-flushing stdio */
!              _exit(1);
          }
          else
!             _exit(0);
      }

      /* in parent */
--- 1109,1129 ----
          fprintf(stderr, "-----------------------------------------\n");
      }

+     /* Flush all stdio channels just before fork,
+      * to avoid double-output problems.
+      */
+     fflush(NULL);
+
      if ((pid = fork()) == 0)
      {  /* child */
          if (DoBackend(port))
          {
              fprintf(stderr, "%s child[%d]: BackendStartup: backend startup failed\n",
!                     progname, (int) getpid());
!              exit(1);
          }
          else
!             exit(0);
      }

      /* in parent */
***************
*** 1130,1135 ****
--- 1155,1161 ----
      }

      bn->pid = pid;
+     bn->cancel_key = MyCancelKey;
      DLAddHead(BackendList, DLNewElem(bn));

      ActiveBackends = TRUE;
***************
*** 1192,1197 ****
--- 1218,1225 ----
      char        dbbuf[ARGV_SIZE + 1];
      int            ac = 0;
      int            i;
+     struct timeval now;
+     struct timezone tz;

      /*
       *    Let's clean up ourselves as the postmaster child
***************
*** 1225,1231 ****
      if (NetServer)
          StreamClose(ServerSock_INET);
      StreamClose(ServerSock_UNIX);
!
      /* Now, on to standard postgres stuff */

      MyProcPid = getpid();
--- 1253,1268 ----
      if (NetServer)
          StreamClose(ServerSock_INET);
      StreamClose(ServerSock_UNIX);
!
!     /*
!      * Don't want backend to be able to see the postmaster random number
!      * generator state.  We have to clobber the static random_seed *and*
!      * start a new random sequence in the random() library function.
!      */
!     random_seed = 0;
!     gettimeofday(&now, &tz);
!     srandom(now.tv_usec);
!
      /* Now, on to standard postgres stuff */

      MyProcPid = getpid();
***************
*** 1365,1374 ****
  static void
  RandomSalt(char *salt)
  {
!     long rand = PostmasterRandom();
!
!     *salt = CharRemap(rand % 62);
!     *(salt + 1) = CharRemap(rand / 62);
  }

  /*
--- 1402,1409 ----
  static void
  RandomSalt(char *salt)
  {
!     *salt = CharRemap(PostmasterRandom());
!     *(salt + 1) = CharRemap(PostmasterRandom());
  }

  /*
***************
*** 1387,1391 ****
          initialized = true;
      }

!     return ramdom() ^ random_seed;
  }
--- 1422,1426 ----
          initialized = true;
      }

!     return random() ^ random_seed;
  }

pgsql-hackers by date:

Previous
From: Bruce Momjian
Date:
Subject: Re: [HACKERS] Re: Cancel key now ready
Next
From: Egon Schmid
Date:
Subject: Re: [HACKERS] Re: Cancel key now ready