加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

为MySQL添加uid

(2016-01-24 14:05:41)
标签:

mysql

uid

分类: mysql源码修改
最近需要进行数据库的国家3级安全测试,其中就有一项是用户UID唯一标识。
MySQL本身并没有uid这个概念,使用host和user作为账户的唯一标识,但这种方式无法区分被删除后重建的账户与之前账户的区别。

例如账户u1@'%'删除后重新建了一个u1@'%',那么从mysql的角度根本无法区分这2个账户的区别。

为了解决这个问题,做了如下几种尝试:

1. 不修改源码,给user表建一个触发器,当新增时同时往另一张新表user_id(host, user, uid)进行插入,其中uid为自增列;删除时从user_id中删除对应的记录。
    但一创建mysql就报错了,所不允许给系统表建立触发器。通过查看代码可知,创建触发器时会进行检查,如果目标表是mysql库下的,直接就报错。具体代码可以看sql/sql_trigger.cc的mysql_create_or_drop_trigger函数。
    那我们是不是可以通过修改代码去掉这个检查呢?
    答案是可以去掉这个检查,但没有意义。因为create user/grant/drop user等账户管理SQL和普通的DML语句走的逻辑代码是完全不一样的,账户管理SQL的处理逻辑并没有对触发器的检查。
    所以触发器方案无效。

2. 在创建新用户的时候是不是可以通过调用DML的insert语句接口往user_id表中插入一行数据?
    注:想使用DML的insert接口是因为只有那个入口的代码逻辑才会处理自增列赋值。
    答案也是否定的,改完后的代码直接出core了,因为mysql要求进行sql操作前需要一次性对需要锁定的表进行上锁,但账户管理SQL和DML的insert接口都会进行表上锁操作,强行整合到一起的话代码改动太大,稳定性没有保证,所以调用DML的insert语句接口的方案也是无效。

3. 修改源码,在创建和删除账户的代码处直接对user_id进行数据写入或删除
    实际上mysql的账户管理SQL经过各种逻辑判断后也是直接对user表进行数据写入或删除,所以可以参照这个代码逻辑额外再多维护一张user_id表。
    账户新增的代码入口为 sql/sql_acl.cc的mysql_create_user函数,大致的处理流程如下:
    a. 打开并获取权限相关的表  open_grant_tables
         注: 在open_grant_tables中,需要将 user_id一起处理
    b. 创建或修改账户 replace_user_table
        在这个函数中会准备出新用户的一行数据,并最终调用
         table->file->ha_write_row(table->record[0]) 进行数据行写入
        所以在这个函数中我们需要在准备user表的新增数据行时,同时也准备user_id表对应的数据行。
        由于这里无法直接调用表自增逻辑,所以可以存储时间戳,本质上来说主要确保每次存储的uid值不相同就可以了,而时间戳正好可以满足这个需求。
        如下是准备user_id表一行数据的代码:
  if (uid_table) {
    time_t rawtime;

    rawtime = time(0);

    time_long = (long)rawtime;
    buff_len = (uint)sprintf(buff, "%ld", time_long);
    if (buff_len >= 512)
      buff_len = 511;
    buff[buff_len] = '\0';
    uid_table->use_all_columns();
    uid_table->field[0]->store(combo.host.str,combo.host.length,
                               system_charset_info);
    uid_table->field[1]->store(combo.user.str,combo.user.length,
                               system_charset_info);
    uid_table->field[2]->store(buff,buff_len,
                               system_charset_info);

  }


  
  最后通过如下代码进行持久化
  if (!old_row_exists && uid_table) { //write uid
    uid_table->file->ha_write_row(uid_table->record[0]);
  }

  当然也可以通过grant语句新建账户,代码入口为sql/sql_acl.cc的 mysql_xxx_grant,他们最终都是调用replace_user_table进行新账户的创建的。

  账户删除的代码入口为sql/sql_acl.cc的mysql_drop_user,大致的处理流程为:
a. 打开并获取权限相关的表  open_grant_tables
b. 进行用户删除,主要的函数调用路径为:
    handle_grant_data-> handle_grant_table,最终通过modify_grant_table进行行记录删除
    我们可以在 handle_grant_table中添加对user_id表的处理,大致的代码如下:

      if (result && drop) {
        TABLE *uid_table = tables[7].table;
        uid_table->use_all_columns();
        uid_table->field[0]->store(host_str, user_from->host.length, system_charset_info);
        uid_table->field[1]->store(user_str, user_from->user.length, system_charset_info);

        key_prefix_length= (uid_table->key_info->key_part[0].store_length +
                            uid_table->key_info->key_part[1].store_length);
        key_copy(user_key, uid_table->record[0], uid_table->key_info, key_prefix_length);

        error= uid_table->file->ha_index_read_idx_map(uid_table->record[0], 0,
                                                  user_key, (key_part_map)3,
                                                  HA_READ_KEY_EXACT);
        if (!error)
          modify_grant_table(uid_table, uid_table->field[0], uid_table->field[1], user_to);
      }


   其中if判定的result是 user表执行数据删除的结果,drop表示是进行数据删除。
   user_id的主键为(host,user)
   大致的处理逻辑为,通过host和user走唯一索引定位出一行数据,将它保存在record[0]中,然后通过modify_grant_table删除这行数据。


至此,uid功能添加完毕。


转载请注明转自高孝鑫的博客!
  

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有