本文来自依云's Blog,转载请注明。
鉴于QQ 4 Linux实在太不好用,有些不得不使用QQ的Linux用户使用Wine来运行QQ,我经过努力也成功了,但依旧不用,因为在去年的3Q大战中我们看到腾讯实在难以信任。想想我的ssh密钥、GPG密钥、getmail和msmtp的配置文件等等非常重要的文件QQ皆可轻易扫描并上传……
想过使用另外的低权限用户来wine QQ。然而即使这样,我自己的众多文件QQ依然可以看到,心中还是不安,而且这样作为QQ的重要功能之一——互传文件——将很不方便。于是终于有一天,我开始折腾chroot了。为了不需要总是sudo,我用C写的,可以使用Linux的suid特性,同时也练习下C编程。在man相关系统调用的时候,我还发现了unshare这个调用,竟然可以把挂载的文件系统设置成只在新的挂载命名空间(mount namespace)中可见。这样就可以把为了chroot而mount --bind的项目全部隐藏起来,免得mount输出一大堆不想看到的东西。至于umount嘛,不管了,反正只是mount --bind的。
/* ===================================================================== * * chrooted wine QQ * ===================================================================== */ #define _GNU_SOURCE #include<unistd.h> #include<sys/stat.h> #include<sys/types.h> #include<sys/mount.h> #include<linux/limits.h> #include<sched.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #define ROOT "/tmp/.rwine" #define PROGRAM "你的QQ的目录" /* 文件共享目录;若不存在会自动创建 */ #define SHAREDIR "哪个目录作为共享?" #define BIN "Bin/QQ.exe" #define ROOTD(dir) ROOT dir #define MKDIR(dir) mkdir(ROOTD(dir), 0777) #define MOUNTBIND(dir) mountbind(dir, ROOTD(dir)) int mountbind(const char *source, const char *target); /* --------------------------------------------------------------------- */ int main(int argc, char **argv){ int ret; ret = unshare(CLONE_NEWNS); if(ret){ perror("unshare"); exit(1); } seteuid(getuid()); ret = mkdir(ROOT, 0777); seteuid(0); if(ret && errno != EEXIST){ perror("mkdir new root dir"); exit(1); } seteuid(getuid()); mkdir(SHAREDIR, 0700); seteuid(0); setgid(0); MKDIR("/lib"); MKDIR("/usr"); MKDIR("/usr/share"); MKDIR("/usr/bin"); MKDIR("/usr/lib"); MKDIR("/etc"); MKDIR("/etc/fonts"); MKDIR("/program"); MKDIR("/dev"); MKDIR("/tmp"); MKDIR("/tmp/share"); chmod(ROOTD("/tmp"), 01777); ret = MOUNTBIND("/lib") || MOUNTBIND("/usr/share") || MOUNTBIND("/usr/bin") || MOUNTBIND("/usr/lib") || MOUNTBIND("/etc/fonts") || MOUNTBIND("/dev") || mountbind(PROGRAM, ROOTD("/program")) || mountbind(SHAREDIR, ROOTD("/tmp/share")); if(ret){ perror("mount --bind"); exit(1); } char path[PATH_MAX]; char path2[PATH_MAX]; char *p; strcpy(path, getenv("HOME")); strcat(path, "/.wine"); strcpy(path2, ROOT); p = strtok(path, "/"); seteuid(getuid()); while(p){ strcat(path2, "/"); strcat(path2, p); mkdir(path2, 0777); p = strtok(NULL, "/"); } seteuid(0); /* path changed by strtok */ strcpy(path, getenv("HOME")); strcat(path, "/.wine"); strcpy(path2, ROOT); strcat(path2, path); ret = mountbind(path, path2); if(ret){ perror("mount --bind user's wine dir"); exit(1); } strcpy(path, getenv("HOME")); strcat(path, "/.fonts"); if(access(path, X_OK) == 0){ strcpy(path2, ROOT); strcat(path2, path); seteuid(getuid()); mkdir(path2, 0777); seteuid(0); ret = mountbind(path, path2); if(ret){ perror("mount --bind user's font dir"); exit(1); } } strcpy(path, getenv("HOME")); strcat(path, "/.fontconfig"); if(access(path, X_OK) == 0){ strcpy(path2, ROOT); strcat(path2, path); seteuid(getuid()); mkdir(path2, 0777); seteuid(0); ret = mountbind(path, path2); if(ret){ perror("mount --bind user's fontconfig dir"); exit(1); } } ret = chroot(ROOT); if(ret){ perror("chroot"); exit(1); } chdir("/tmp"); ret = setuid(getuid()); if(ret){ perror("setuid"); exit(1); } execl("/usr/bin/wine", "/usr/bin/wine", "/program/"BIN, NULL); /* execl("/usr/bin/wine", "/usr/bin/wine", "notepad", NULL); */ perror("execl"); return 1; } int mountbind(const char *source, const char *target){ return mount(source, target, NULL, MS_BIND | MS_NOSUID, NULL); } /* ===================================================================== * * vim modeline * * vim:se fdm=expr foldexpr=getline(v\:lnum)=~'^\\S.*{'?'>1'\:1: * * ===================================================================== */
程序名为rwine。这里的“r”可不是rsync或者rcp中的“r”,而是“rbash”、“rzsh”、“rvim”中的“r”的意思。在代码的最开头有几个宏是用来配置路径的。suid程序嘛,像这种东西还是硬编码进去好了。因为只是自己使用,所以没有做太多的错误检查之类的,健壮性应该不怎么样。对于这个程序我只要没有安全漏洞就OK了。
另外说个小插曲。在调试这段代码的时候,我发现用户的路径总是挂载得不对,检查了好久,才注意到strtok的声明是
char *strtok(char *str, const char *delim);
第一个参数那里没有const
!再仔细看看man文档,果然,它把传进去的参数给改了。
Feb 27, 2011 11:51:21 PM
我想webqq应该没这个顾虑吧...
Feb 28, 2011 01:24:11 AM
是的,webqq 是安全的,但是,它除了文本聊天之外其它的都不好用(图片经常失败、传文件少有成功、不支持远程协助)。而且,由于同在浏览器中,我会因为输入法状态的混乱而经常搞错。
PS:你留言重复了,我删掉了一个。
May 04, 2011 10:59:50 PM
哇卡,,你太能折腾啦我直接vbox+xp就ok了。。嘿嘿
May 04, 2011 11:11:20 PM
那个太耗资源了……我的内存不够,CPU散热也不好。。。
而且,我不是想学下 Linux C 编程么?
Sep 02, 2011 01:22:39 AM
Linux 3.0-ARCH x86_64
rwine.c:26:3: 警告:隐式声明函数‘unshare’ [-Wimplicit-function-declaration]
rwine.c:26:17: 错误:‘CLONE_NEWNS’未声明(在此函数内第一次使用)
rwine.c:26:17: 附注:每个未声明的标识符在其出现的函数内只报告一次
Sep 02, 2011 11:44:37 AM
已更新,要 #define _GNU_SOURCE。
Sep 08, 2011 02:57:26 PM
其实可以用现成的 fakechroot 来干,
或者用 python-fuse 简单改下默认例子来过滤文件系统,
再不使用 LD_PRELOAD 也行(过滤掉无关目录的 open)
不过,既然是 wine 程序,其实最简单还是弄一个独立的 WINEPREFIX 目录,然后改掉里边的 C: Z: 之类盘符映射
Sep 08, 2011 03:16:38 PM
当时没想到用 LD_PRELOAD 机制呢。不过:
fakechroot should not be used as a tool for enhancing system security i.e. by separating (sandboxing) applications. It is very easy to escape from a fake chroot environment.
似乎不够安全呢。而且也同样需要 mount --bind 以及 unshare,所以还得用 sudo。
自己写 LD_PRELOAD 库也不错,不过在这好久以后我才会写呢。
Wine 的配置我就从来没研究过了。没事不用浪费时间在它上面。
Sep 08, 2011 03:37:46 PM
不需要特地去看配置的,简单执行下
ls -lh ~/.wine/dosdevices/
就知道怎么回事了。
而独立 WINEPREFIX 就是加这个环境变量指定别的目录,
比如
env WINEPREFIX=~/.wine-qq/ wine notepad
(然后看 ~/.wine-qq/dosdevices/ 下的软链