Bug #12925 GET_LL in my_getopt.c is buggy

Submitted: 1 Sep 2005 7:45 Modified: 31 Oct 2005 19:31
Reporter: Osku Salerma Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server Severity:S2 (Serious)
Version:4.1, 5.0 OS:Any 32-bit target
Assigned to: Jim Winstead CPU Architecture:Any

[1 Sep 2005 7:45] Osku Salerma
Description:
While working on bug #12701, I changed some innobase config variables to type longlong, and changed them to use GET_LL in mysqld.cc and upped their maximum values to LONGLONG_MAX. Then I added code to innobase to detect values over 4GB on 32-bit machines and error out. To my surprise that code never triggered. Adding debug prints, I found out no matter what I put to my config file, the values were clamped to 4095MB before being handed to innobase.

Diving into the option handling code revealed the following:

    case GET_INT:
    case GET_UINT:           /* fall through */
      *((int*) result_pos)= (int) getopt_ll(argument, opts, &err);
      break;
    case GET_LONG:
    case GET_ULONG:          /* fall through */
      *((long*) result_pos)= (long) getopt_ll(argument, opts, &err);
      break;
    case GET_LL:
      *((longlong*) result_pos)= getopt_ll(argument, opts, &err);
      break;
    case GET_ULL:
      *((ulonglong*) result_pos)= getopt_ull(argument, opts, &err);
      break;

and getopt_ll:

static longlong getopt_ll(char *arg, const struct my_option *optp, int *err)
{
  longlong num;
  ulonglong block_size= (optp->block_size ? (ulonglong) optp->block_size : 1L);
  
  num= eval_num_suffix(arg, err, (char*) optp->name);
  if (num > 0 && (ulonglong) num > (ulonglong) (ulong) optp->max_value &&
      optp->max_value) /* if max value is not set -> no upper limit */
    num= (longlong) (ulong) optp->max_value;
  num= ((num - (longlong) optp->sub_size) / block_size);
  num= (longlong) (num * block_size);

  return max(num, optp->min_value);
}

The problem is that optp->max_value is cast to ulong, which is wrong for longlong values. Indeed, if I changed the variable to use getopt_ull, which is almost the same but doesn't do the ulong casting, the code worked.

How to repeat:
Change some config variable to type longlong and using GET_LL and having maximum value over 4GB, put that value in the config file as something over 4GB, observe how it's clamped to 4095MB.

Suggested fix:
I could fix GET_LL myself, but I'm not sure why int/uint/long/ulong/longlong are all using the same function (getopt_ll), since it's obviously problematic, so would prefer someone who knows the code to do it.
[1 Sep 2005 12:18] Hartmut Holzgraefe
the double cast

  (ulonglong) (ulong) optp->max_value

clearly looks like some sort of copy/paste bug to me
[2 Sep 2005 23:13] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/internals/29261
[2 Sep 2005 23:14] Jim Winstead
problem is in 4.1, too.
[5 Sep 2005 7:55] Osku Salerma
The patch is woefully inadequate. After applying it to my tree and having

innodb_buffer_pool_size=25600M

in my ~/.my.cnf, and adding a printf to getopt_ll so I know what it's returning I get this:

getopt_ll: '25600M', 9223372036853727232

i.e. it's returning optp->max_value - 1 MB, probably because the patch still leaves one ulong cast, in the if condition.

I can't believe the test case added:

DIE_UNLESS(opt_getopt_ll_test > LL(4*1024*1024*1024));

It's checking that the value parsed is bigger than 4GB, but not checking that it's actually the correct value? What's up with that?

Now, if I modify the code like this:

   num= eval_num_suffix(arg, err, (char*) optp->name);
-  if (num > 0 && (ulonglong) num > (ulonglong) (ulong) optp->max_value &&
+  if (num > 0 && (ulonglong) num > (ulonglong) optp->max_value &&
       optp->max_value) /* if max value is not set -> no upper limit */
-    num= (longlong) (ulong) optp->max_value;
+    num= optp->max_value;
   num= ((num - (longlong) optp->sub_size) / block_size);
   num= (longlong) (num * block_size);

it seems to work:

getopt_ll: '25600M', 26843545600

However, I'm not sure what those ulonglong casts are doing there since this is dealing only with values that fit in a longlong, and also, why a max_value of 0 is special-cased as "no max value" instead of simply requiring every config value to supply correct max values. That's a way bigger change than I'm prepared to make, however.
[12 Sep 2005 17:09] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/internals/29677
[14 Sep 2005 1:44] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/internals/29781
[23 Sep 2005 11:40] Osku Salerma
Jim, any ETA on this? This is blocking bug #12701, and proper support for 64-bit Windows would be nice to have.
[28 Oct 2005 1:56] Jim Winstead
Fixed in 4.1.16 and 5.0.16.
[31 Oct 2005 19:31] Paul DuBois
Noted in 4.1.16, 5.0.16 changelogs.

Portions of this website are copyright © 2001, 2002 The PHP Group

Page generated in 0.065 sec. using MySQL 8.4.7-u6-cloud

Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the respective copyright holders.
It is not reviewed in advance by Oracle and does not necessarily represent the opinion of Oracle or any other party.