golang mysql 诊断之旅(2000万开房数据被曝光引发的血案)
来源:互联网 发布:网络科学导论论文 编辑:程序博客网 时间:2024/06/04 23:34
最近由于某某漏洞原因,2000万开房数据被曝光,数据是csv格式,打开慢的要死,于是想把这2000w的开房数据导入mysql,然后用go写个简单的查询工具。
悲剧开始了:
第一步,下载 mysql模块,go get github.com/go-sql-driver/mysql,
第二步,写个小例子测试下
package mainimport ("database/sql" //这包一定要引用"encoding/json""fmt" //这个前面一章讲过_ "github.com/go-sql-driver/mysql" //这就是刚才下载的包)// 定义一个结构体, 需要大写开头哦, 字段名也需要大写开头哦, 否则json模块会识别不了// 结构体成员仅大写开头外界才能访问type User struct {User string `json:"user"`Password string `json:"password"`Host string `json:"host"`}// 一如既往的main方法func main() {// 格式有点怪, @tcp 是指网络协议(难道支持udp?), 然后是域名和端口db, e := sql.Open("mysql", "root:@tcp(192.168.7.15:3306)/mysql?charset=utf8")if e != nil { //如果连接出错,e将不是nil的print("ERROR?")return}defer db.Close()// 提醒一句, 运行到这里, 并不代表数据库连接是完全OK的, 因为发送第一条SQL才会校验密码 汗~!rows, e := db.Query("select user,password,host from mysql.user")if e != nil {fmt.Printf("query error!!%v\n", e)return}if rows == nil {print("Rows is nil")return}fmt.Println("DB rows.Next")for rows.Next() { //跟java的ResultSet一样,需要先next读取user := new(User)// rows貌似只支持Scan方法 继续汗~! 当然,可以通过GetColumns()来得到字段顺序row_err := rows.Scan(&user.User, &user.Password, &user.Host)if row_err != nil {print("Row error!!")return}b, _ := json.Marshal(user)fmt.Println(string(b)) // 这里没有判断错误, 呵呵, 一般都不会有错吧}fmt.Println("Done")}
结果一直报错:
panic: runtime error: index out of rangegoroutine 1 [running]:github.com/go-sql-driver/mysql.readLengthEncodedInteger(0x10fb0037, 0x1, 0xfc9, 0x0, 0x0, ...)E:/go/src/github.com/go-sql-driver/mysql/utils.go:406 +0x3e8github.com/go-sql-driver/mysql.skipLengthEnodedString(0x10fb0037, 0x1, 0xfc9, 0x2, 0x0, ...)E:/go/src/github.com/go-sql-driver/mysql/utils.go:366 +0x38github.com/go-sql-driver/mysql.(*mysqlConn).readColumns(0x10f88230, 0x1, 0x10f86500, 0x1, 0x1, ...)E:/go/src/github.com/go-sql-driver/mysql/packets.go:482 +0x389github.com/go-sql-driver/mysql.(*mysqlConn).getSystemVar(0x10f88230, 0x530b88, 0x12, 0x0, 0x0, ...)E:/go/src/github.com/go-sql-driver/mysql/connection.go:228 +0x118github.com/go-sql-driver/mysql.(*mysqlDriver).Open(0x5f0bf4, 0x547aa8, 0x2f, 0x1, 0x10f9f900, ...)E:/go/src/github.com/go-sql-driver/mysql/driver.go:70 +0x2dedatabase/sql.(*DB).conn(0x10f85e40, 0x10f50228, 0xff014c, 0x5)C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/database/sql/sql.go:484 +0x15edatabase/sql.(*DB).query(0x10f85e40, 0x527b68, 0x8, 0x0, 0x0, ...)C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/database/sql/sql.go:708 +0x58database/sql.(*DB).Query(0x10f85e40, 0x527b68, 0x8, 0x0, 0x0, ...)C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/database/sql/sql.go:699 +0x6bmain.main()E:/go/src/testmysql/testmysql.go:54 +0x89goroutine 3 [syscall]:syscall.Syscall6(0x7c80a7bd, 0x5, 0xf70, 0x10f86420, 0x10f50280, ...)C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/runtime/zsyscall_windows_windows_386.c:97 +0x49syscall.GetQueuedCompletionStatus(0xf70, 0x10f86420, 0x10f50280, 0x10f50278, 0xffffffff, ...)C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/syscall/zsyscall_windows_386.go:507 +0x7enet.(*resultSrv).Run(0x10f50260)C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/net/fd_windows.go:150 +0x11acreated by net.startServerC:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/net/fd_windows.go:285 +0xdegoroutine 4 [select]:net.(*ioSrv).ProcessRemoteIO(0x10f50268)C:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/net/fd_windows.go:183 +0x171created by net.startServerC:/Users/ADMINI~1/AppData/Local/Temp/2/bindist465310315/go/src/pkg/net/fd_windows.go:293 +0x163exit status 2
刚开始怀疑windows没有装mysql驱动,可是兴趣来了想分析下堆栈信息,正好学点新东西,
log大法,跟踪readLengthEncodedInteger,并加入如下调试代码,打印b[]byte的内存信息,发现b[0] = 0xfe,但是后面却没有数据了,所以造成了数组b的index溢出,完整代码如下:
func readLengthEncodedInteger(b []byte) (num uint64, isNull bool, n int) {fmt.Println(b)fmt.Printf("0x%02x\n", b[0])switch b[0] {// 251: NULLcase 0xfb:n = 1isNull = truereturn// 252: value of following 2case 0xfc:num = uint64(b[1]) | uint64(b[2])<<8n = 3return// 253: value of following 3case 0xfd:num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16n = 4return// 254: value of following 8case 0xfe:num = uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |uint64(b[7])<<48 | uint64(b[8])<<54n = 9return}// 0-250: value of first bytenum = uint64(b[0])n = 1return}
继续跟踪发现:package.go的readColumns中有一个很奇怪的退出,但是没有这个0xfe的处理,而且奇怪的是for循环只有这里可以正常return。。。
func (mc *mysqlConn) readColumns(count int) (columns []mysqlField, err error) {var data []bytevar i, pos, n intvar name []bytecolumns = make([]mysqlField, count)fmt.Println("count:")fmt.Println(count)for {data, err = mc.readPacket()if err != nil {return}// EOF Packetif data[0] == iEOF && len(data) == 5 {if i != count {err = fmt.Errorf("ColumnsCount mismatch n:%d len:%d", count, len(columns))}return}
发现重要说明,这个Column的包后面有一个EOF_Packet,也就是上面的0xfe,于是怀疑这是go mysql driver的一个bug
15.6.5.1. COM_FIELD_LIST responseThe response to a COM_FIELD_LIST can either be aa ERR_Packet orone or more Column Definition packets and a closing EOF_Packet
于是到go-sql-driver的官网 https://github.com/go-sql-driver/mysql/blob/master/packets.go,想pull->提交这个bug的patch,结果。。。
我擦!!这个issue已经在github上修改了,如下:
// EOF Packet if data[0] == iEOF && (len(data) == 5 || len(data) == 1) { if i == count { return columns, nil } return nil, fmt.Errorf("ColumnsCount mismatch n:%d len:%d", count, len(columns)) }
顿时崩溃啊,猜想go get下来的代码是旧版本的,看README.md
**Current tagged Release:** June 03, 2013 (Version 1.0.1)
再看github上
**Current tagged Release:** May 14, 2013 (Version 1.0)
疑问来了,虽然github库上的release版本还是旧的,但是go get下来的新版本存在这个问题!!!???
解决方案:唯一的方式就是取一个github上的版本git clone https://github.com/go-sql-driver/mysql.git,不要用go get的版本。
最后,哪位大神解释下 go get和git clone下来的版本为什么不一样??
补充:
1.@ASTA谢: 因为你之前安装过github.com/go-sql-driver/mysql,所以你go get的时候不会更新,你必须使用go get -u更新,而git clone是最新版本的更新(2013-10-24 16:23:57),这个go get -u 是更新,但不是问题所在,@jimmykuu:默认是去获取tag为go1的代码,果然如jimmykuu所说:我查看tag为go1的代码:https://github.com/go-sql-driver/mysql/tree/go1,和go get下来的相同,而且golang上有说明。。。"When checking out or updating a package, get looks for a branch or tag that matches the locally installed version of Go. The most important rule is that if the local installation is running version "go1", get searches for a branch or tag named "go1". If no such version exists it retrieves the most recent version of the package."
所以我的结论就是go-sql-driver中的go1 tag充满bug,不负责任!
2. 刚才看了下,之所以其他人没有碰到这个问题,是因为我正好用的古老版本mysql4.1 ,补丁在这fix crash when connect to mysql4.1:https://github.com/go-sql-driver/mysql/commit/4a178617b97609ebd4d4a0ae5791225540c1bb26#diff-2357b8494bbd2f27c09e61fc8ef5f092
3.用git clone的新版本替换后,切记删掉pkg下生成的mysql.a文件!!
- golang mysql 诊断之旅(2000万开房数据被曝光引发的血案)
- 数据质量引发的血案
- 由2000W多条开房数据引发的思考、实践 --IO
- 从2000万条开房数据优化谈检索
- 2000万开房数据【多线程】写入同一文件
- mysql字段名冲突引发的血案
- mysql字段名冲突引发的血案
- Mysql 一条update语句引发的血案
- Android填坑之旅(第十篇)AndroidStudio中VersionCode引发的血案
- Android填坑之旅(第十二篇)由于Butterknife引发的血案
- android之Dialog自定义引发的血案
- 统计信息被锁定引发的血案
- ExecutorService引发的血案(一)结构
- ExecutorService引发的血案(三)ThreadPoolExecutor
- WSAPoll引发的一场血案(1)
- WSAPoll引发的一场血案(2)
- ActiveX引发的“血案”
- size_t引发的血案
- php jquery 验证码 输入后验证表单
- python sys模块
- VC操纵EXCEL的两种方法
- MVC的理解
- 2.14 Displaying an Image on a Navigation Bar
- golang mysql 诊断之旅(2000万开房数据被曝光引发的血案)
- 面试中常见小而难的题
- android关于scrollview嵌套ExpandableListView的实现1
- java.lang.IncompatibleClassChangeError: class net.sf.cglib.core.DebuggingClassWriter has interface o
- Oracle WebLogic Server 与 Oracle Database 12c 的集成 (2)
- 文件上传 如何读取apk文件的 包名、版本号、图标?
- UITextField的圆角和常用样式
- Ubuntu12.04 64位安装Foxit Reader
- Navicat 数据模型