Wednesday, June 25, 2008

ruby 可执行任意代码漏洞

今天看 slashdot,才知道 ruby 最近爆出的非常严重的漏洞,而我居然全然不知,出了一身冷汗,搜索订阅的 RSS 新闻,果然早有文章提起,夹杂在大量资讯中间,大约被直接忽视掉了。幸好我的项目还没有上线,以最快的速度订阅了 ruby-core 的 mailing list,下载 1.8.6 p230 开始升级。

去掉 debian 的 patches,dpkg-buildpackage 很顺利,安装过后,重启服务,随意访问了几个页面,出现 500 错误。检查 log:

ActionView::TemplateError (wrong argument type FalseClass (expected Proc))
*** glibc detected *** /usr/bin/ruby1.8: corrupted double-linked list: 0x00000000014eb220 ***
======= Backtrace: =========
/lib/libc.so.6[0x7ff36c97e01d]
/lib/libc.so.6[0x7ff36c97e146]
/lib/libc.so.6[0x7ff36c9802cc]
...

google 一下,发现我并不孤独,升级到 1.8.6 p230 后,都遇到了 segment fault 等问题。而奇怪的是,到目前位置,官方仍然没有一个解决方案,目前存在的解决方案和进展:

Friday, June 20, 2008

防作弊评分算法

Web 2.0 网站中最常见的功能之一就是用户投票,常见的可选分值范围为 [1,2,3,4,5],最终得分则等于用户评价的数值平均头。这种算法有个显而易见的漏洞,容易作弊,比如某内容共计一用户投了 5 分,则此项最终评分为 5。

理想的效果是评价数越少,得分越低,最简单的解决办法是 bayesian average。贝叶斯平均使得单项评分更往平均评分靠近,对评价总数低的条目更加明显。

    b(r) = [ W(a) * a + W(r) * r ] / (W(a) + W(r)]

    r = average rating for an item
    W(r) = weight of that rating, which is the number of ratings
    a = average rating for your collection
    W(a) = weight of that average, which is an arbitrary number, but should be higher if you generally expect to have more ratings for your items; 100 is used here, for a database which expects many ratings per item
    b(r) = new bayesian rating 
    b(r) = [100 * 6.50 + 3 * 10] / (100 + 3)
    b(r) = 680 / 103
    b(r) = 6.60

当 W(a) 足够大的时候,少量的作弊无法明显改变结果。

Wednesday, June 18, 2008

在 lenny 上降级 ruby 解决 rails 2.0 不兼容 ruby 1.8.7 问题

production 系统用的是 lenny,前两天 upgrade 了一把,ruby 1.8.7,当时并未在意,直到今天 cap deploy 后,部分页面出错,检查了一把,代码没问题,看来应该是升级到 1.8.7 后出现的问题了。google 了一番,的确如此:

Debian bug#484351 中有人指出最近的 rails 2.1.0 修正了1.8.7 兼容问题,事实上官方下载页面明确指出了 rails 还不兼容 ruby 1.8.7,看来升级到 rails 2.1.0 并不能解决这个问题,只能降级 ruby 了。

好在 debian 中还有 ruby 1.8.6 的 source,自己动手 build 吧。

wget http://debian.cn99.com/debian/pool/main/r/ruby1.8/ruby1.8_1.8.6.114-2.diff.gz
wget http://debian.cn99.com/debian/pool/main/r/ruby1.8/ruby1.8_1.8.6.114.orig.tar.gz
tar zxvf ruby1.8_1.8.6.114.orig.tar.gz
gzip -d ruby1.8_1.8.6.114-2.diff.gz
cd ruby-1.8.6-p114
aptitude build-dep ruby
dpkg-buildpackage
cd ../
dpkg -i --force-all irb1.8_1.8.6.114-2_all.deb libopenssl-ruby1.8_1.8.6.114-2_amd64.deb libreadline-ruby1.8_1.8.6.114-2_amd64.deb libruby1.8_1.8.6.114-2_amd64.deb rdoc1.8_1.8.6.114-2_all.deb ruby1.8_1.8.6.114-2_amd64.deb ruby1.8-dev_1.8.6.114-2_amd64.deb ruby1.8-elisp_1.8.6.114-2_all.deb
降级到 ruby 1.8.6 后 mongrel 的依赖出现问题,同样需要 rebuild,好在这个时候重启 mongrel,问题已经解决了。 如果希望能够解决 mongrel 的依赖问题的话:
apt-get source libsetup-ruby
cd libsetup-ruby-3.4.1
dpkg-buildpackage
cd ../
dpkg -i libsetup-ruby1.8_3.4.1-4_all.deb
apt-get source ruby-pkg-tools
cd ruby-pkg-tools-0.13
dpkg-buildpackage
cd ../
dpkg -i  ruby-pkg-tools_0.13_all.deb
rebuild 需要满足 rdoc 包,rebuild rdoc 的话需要一堆的依赖,我偷懒了一把
ln -s /usr/bin/rdoc1.8 /usr/bin/rdoc
apt-get source mongrel
cd mongrel-1.1.5
dpkg-buildpackage
cd ../
dpkg -i  mongrel_1.1.5-2_amd64.deb
依赖解决后,需要 hold ruby 1.8.6 的包,防止 upgrade 的时候再次升级。
aptitude hold irb1.8 libopenssl-ruby1.8 libreadline-ruby1.8 libruby1.8 mongrel rdoc1.8 ruby1.8 ruby1.8-dev ruby1.8-elisp

Friday, June 13, 2008

rails tips: 动态载入 JavaScript 文件

rails 中一般的 JavaScript 机制在 layout 中用 javascript_include_tag 载入需要的脚本文件,对默认整站需要的脚本文件来说很方便,但实际情况是我们经常需要在某些页面动态载入需要的脚本文件来避免调用过多外部文件影响页面载入速度。rails 提供了 javascript_include_tag 和 register_javascript_include_default 都可以用来载入脚本文件,register_javascript_include_default 每次调用都会增加一项到 @@javascript_default_sources,会导致重复载入同一文件,文档中已说明 register_javascript_include_default 用来在 plugin 载入时更改默认载入脚本。所以正确的方法应该是 javascript_include_tag。

javascript_include_tag 后调用直接输出 html script 标记,意味着脚本在 body 解释时才会载入,而我希望的结果是放置到 head 标记内。第一反应是搜索文档看是否存在 response 动态增加载入 javascript 的方法,rails 并没有提供此方法。正觉得无奈的时候忽然想到 rails layout 中用到了 yield,这里不是可以用 yield 达到同样的结果吗。 layouts/application.thml.erb

<%= javascript_include_tag :defaults %>
<%= yield :javascript_files %>
在需要载入的模板文件中:
<% content_for :javascript_files do -%>
<%= javascript_include_tag("some_file_to_load_on_demand.js") -%>
<% end -%>

Tuesday, June 03, 2008

用 monit 自动启动 mongrel 和 backgroundrb 服务

Capistrano 部署 Rails 项目速记中解决了代码更新时候 mongrel/backgroundrb 重启等问题,还需要保证系统重启时服务能够自动启动。

mongrel cluster 自带了 mongrel-cluster 启动管理脚本,Debian 系统的话链接 mongrel_cluster.yml 到 /etc/mongrel-cluster/sites-enabled/ 目录,系统启动的时候 /etc/init.d/mongrel-cluster 会自动启动 mongrel daemon,然而 backgroundrb 的启动问题还是没有解决,需要手工写启动脚本,google 了一番之后,找到 Monit 这个小工具:

# 摘自 monit deb package description.
A utility for monitoring and managing daemons or similar programs
 monit is a utility for monitoring and managing daemons or similar
 programs running on a Unix system. It will start specified programs
 if they are not running and restart programs not responding.
 .
 monit supports:
  * Daemon mode - poll programs at a specified interval
  * Monitoring modes - active, passive or manual
  * Start, stop and restart of programs
是否可以用 Monit 来启动 mongrel 和 backgroundrb 呢,答案是肯定的,事实上这也应该是推荐方式。

安装 Monit

$sudo aptitude install monit
修改 /etc/default/monit 配置:
startup=1
CHECK_INTERVALS=30 # monit 监控检测时间价格,推荐设置成 30 秒
配置 monit
$sudo grep '^[^#]' /etc/monit/monitrc
set daemon  30
set logfile syslog facility log_daemon
set httpd port 2812 and
     use address localhost  # only accept connection from localhost
     allow localhost        # allow localhost to connect to the server and
include /etc/monit/conf.d/*
$sudo mkdir /etc/monit/conf.d

配置 Daemon 项

$cat 001_neo_mongrel.conf
check process mongrel_neo_8000
  with pidfile /project/code/path/shared/pids/mongrel.8000.pid
  start program = "/usr/bin/mongrel_rails cluster::start -C /project/code/path/current/config/mongrel_cluster.yml --clean --only 8000"
  stop program = "/usr/bin/mongrel_rails cluster::stop -C /project/code/path/current/config/mongrel_cluster.yml --only 8000"
  if totalmem is greater than 110.0 MB for 4 cycles then restart
  if cpu is greater than 80% for 4 cycles then restart
  if 20 restarts within 20 cycles then timeout
  group neo

check process mongrel_neo_8001
  with pidfile /project/code/path/shared/pids/mongrel.8001.pid
  start program = "/usr/bin/mongrel_rails cluster::start -C /project/code/path/current/config/mongrel_cluster.yml --clean --only 8001"
  stop program = "/usr/bin/mongrel_rails cluster::stop -C /project/code/path/current/config/mongrel_cluster.yml --only 8001"
  if totalmem is greater than 110.0 MB for 4 cycles then restart
  if cpu is greater than 80% for 4 cycles then restart
  if 20 restarts within 20 cycles then timeout
  group neo

check process mongrel_neo_8002
  with pidfile /project/code/path/shared/pids/mongrel.8002.pid
  start program = "/usr/bin/mongrel_rails cluster::start -C /project/code/path/current/config/mongrel_cluster.yml --clean --only 8002"
  stop program = "/usr/bin/mongrel_rails cluster::stop -C /project/code/path/current/config/mongrel_cluster.yml --only 8002"
  if totalmem is greater than 110.0 MB for 4 cycles then restart
  if cpu is greater than 80% for 4 cycles then restart
  if 20 restarts within 20 cycles then timeout
  group neo
$ cat 003_neo_brb.conf
check process brb_imigu_11006
  with pidfile /project/code/path/shared/pids/backgroundrb_11006.pid
  start program = "/bin/su -c 'cd  /project/code/pathcurrent && RAILS_ENV=production nohup ./script/backgroundrb start > /dev/null 2>&1' josh"
  stop program = "/bin/su -c 'cd /home/josh/www/imigu/current && RAILS_ENV=production && ./script/backgroundrb stop' josh"
  group neo

启动 Monit 和监测

$sudo /etc/init.d/monit restart
$sudo monit -c /etc/monit/monitrc status #查看系统状态
通过 monit 已经可以自动启动管理 mongrel 和 backgroundrb,那么谁来保证 monit 的稳定运行呢,答案是 linux 系统 init。 编辑 /etc/inittab 加入:
# Run monit in standard run-levels
mo:2345:respawn:/usr/sbin/monit -Ic /etc/monit/monitrc
mo:06:wait:/usr/sbin/monit -Ic /etc/monit/monitrc stop all
重启 monit
$sudo /etc/init.d/monit stop
$sudo telinit q

参考:

  • Monit man page
  • 2008, Ezra Zygmuntowicz and Bruce Tate, Deploying Rails Applications