Edgewall Software
Modify

Opened 14 years ago

Closed 14 years ago

#8969 closed defect (fixed)

IntegrityError: columns cookie, ipnr, name are not unique

Reported by: sistemas@… Owned by: Remy Blank
Priority: normal Milestone: 0.12.2
Component: general Version: 0.11.1
Severity: normal Keywords: session, pysqlite
Cc: Branch:
Release Notes:

Fixed purging of auth cookies.

API Changes:
Internal Changes:

Description

First of all, I have read tickets #2570, #7096 and #8052. The solution proposed is to upgrade to 0.11, which we are already using.

I have confirmed trac/util/init.py uses sha instead of md5.

We have been using 0.11.1 for a few months already. This error has appeared at a time when we have started to try out SSL client authentication, but won't disappear after we have disabled it, so we aren't sure whether there is a cause-effect relationship.

Cleaning up the browser cookies or restarting the apache server won't help.

Trac detected an internal error:

IntegrityError: columns cookie, ipnr, name are not unique

Cookie ID has been deliberately obscured in the following traceback:

Python Traceback
Most recent call last:

    * File "/usr/lib/python2.4/site-packages/trac/web/main.py", line 423, in _dispatch_request
      Code fragment:
       418. try:
       419. if not env and env_error:
       420. raise HTTPInternalError(env_error)
       421. try:
       422. dispatcher = RequestDispatcher(env)
       423. dispatcher.dispatch(req)
       424. except RequestDone:
       425. pass
       426. resp = req._response or []
       427.  
       428. except HTTPException, e:
      Local variables:
      Name	Value
      after 	[u' except RequestDone:', u' pass', u' resp = ...
      before 	[u' try:', u' if not env and env_error:', u' raise ...
      dispatcher 	<trac.web.main.RequestDispatcher object at 0x2ad92d151050>
      e 	<_sqlite.IntegrityError instance at 0x2ad92f4a80e0>
      env 	<trac.env.Environment object at 0x2ad92b8a1810>
      env_error 	None
      exc_info 	(<class _sqlite.IntegrityError at 0x2ad92ca71d10>, <_sqlite.IntegrityError ...
      filename 	'/usr/lib/python2.4/site-packages/trac/web/main.py'
      frames 	[{'function': '_dispatch_request', 'lines_before': [u' try:', u' ...
      has_admin 	True
      line 	u' dispatcher.dispatch(req)'
      lineno 	422
      message 	u'IntegrityError: columns cookie, ipnr, name are not unique'
      req 	<Request "GET u'/login'">
      resp 	[]
      tb 	<traceback object at 0x2ad92f4a81b8>
      tb_hide 	None
      traceback 	'Traceback (most recent call last):\n File ...
    * File "/usr/lib/python2.4/site-packages/trac/web/main.py", line 197, in dispatch
      Code fragment:
       192. req.args.get('__FORM_TOKEN') != req.form_token:
       193. raise HTTPBadRequest('Missing or invalid form token. '
       194. 'Do you have cookies enabled?')
       195.  
       196. # Process the request and render the template
       197. resp = chosen_handler.process_request(req)
       198. if resp:
       199. if len(resp) == 2: # Clearsilver
       200. chrome.populate_hdf(req)
       201. template, content_type = \
       202. self._post_process_request(req, *resp)
      Local variables:
      Name	Value
      chosen_handler 	<trac.web.auth.LoginModule object at 0x2ad92d151590>
      chrome 	<trac.web.chrome.Chrome object at 0x2ad92bf777d0>
      err 	(<class _sqlite.IntegrityError at 0x2ad92ca71d10>, <_sqlite.IntegrityError ...
      handler 	<trac.web.auth.LoginModule object at 0x2ad92d151590>
      req 	<Request "GET u'/login'">
      self 	<trac.web.main.RequestDispatcher object at 0x2ad92d151050>
    * File "/usr/lib/python2.4/site-packages/trac/web/auth.py", line 101, in process_request
      Code fragment:
        96. def match_request(self, req):
        97. return re.match('/(login|logout)/?$', req.path_info)
        98.  
        99. def process_request(self, req):
       100. if req.path_info.startswith('/login'):
       101. self._do_login(req)
       102. elif req.path_info.startswith('/logout'):
       103. self._do_logout(req)
       104. self._redirect_back(req)
       105.  
       106. # Internal methods
      Local variables:
      Name	Value
      req 	<Request "GET u'/login'">
      self 	<trac.web.auth.LoginModule object at 0x2ad92d151590>
    * File "/usr/lib/python2.4/site-packages/trac/web/auth.py", line 140, in _do_login
      Code fragment:
       135.  
       136. cookie = hex_entropy()
       137. db = self.env.get_db_cnx()
       138. cursor = db.cursor()
       139. cursor.execute("INSERT INTO auth_cookie (cookie,name,ipnr,time) "
       140. "VALUES (%s, %s, %s, %s)", (cookie, remote_user,
       141. req.remote_addr, int(time.time())))
       142. db.commit()
       143.  
       144. req.authname = remote_user
       145. req.outcookie['trac_auth'] = cookie
      Local variables:
      Name	Value
      cookie 	'af8b74xxxxxxxxxxxxxxxx4039'
      cursor 	<trac.db.util.IterableCursor object at 0x2ad92f4a5408>
      db 	<trac.db.pool.PooledConnection object at 0x2ad92f4a2ea8>
      remote_user 	'myuser'
      req 	<Request "GET u'/login'">
      self 	<trac.web.auth.LoginModule object at 0x2ad92d151590>
    * File "/usr/lib/python2.4/site-packages/trac/db/util.py", line 50, in execute
      Code fragment:
        45. def execute(self, sql, args=None):
        46. # -- In case of SQL errors, uncomment the following 'print' statements
        47. # print 'execute', repr(sql)
        48. if args:
        49. # print repr(args)
        50. return self.cursor.execute(sql_escape_percent(sql), args)
        51. return self.cursor.execute(sql)
        52.  
        53. def executemany(self, sql, args=None):
        54. # print 'executemany', repr(sql)
        55. if args:
      Local variables:
      Name	Value
      args 	('af8b74xxxxxxxxxxxxxxxx4039', 'myuser', '10.0.0.105', 1263479383)
      self 	<trac.db.util.IterableCursor object at 0x2ad92f4a5408>
      sql 	'INSERT INTO auth_cookie (cookie,name,ipnr,time) VALUES (%s, %s, %s, %s)'
    * File "/usr/lib64/python2.4/site-packages/sqlite/main.py", line 255, in execute
      Code fragment:
       250. parms = _quoteall(parms[0])
       251. else:
       252. parms = self._unicodeConvert(parms)
       253. parms = tuple(map(_quote, parms))
       254.  
       255. self.rs = self.con.db.execute(SQL % parms)
       256.  
       257. self.closed = 0
       258. self.current_recnum = 0
       259.  
       260. self.rowcount, self._real_rowcount = [len(self.rs.row_list)] * 2
      Local variables:
      Name	Value
      SQL 	'INSERT INTO auth_cookie (cookie,name,ipnr,time) VALUES (%s, %s, %s, %s)'
      parms 	("'af8b74047249852d148c3da0590b4039'", "'myuser'", "'10.0.0.105'", ...
      self 	<trac.db.sqlite_backend.SQLiteUnicodeCursor instance at 0x2ad92f4a2fc8>

File "/usr/lib/python2.4/site-packages/trac/web/main.py", line 423, in _dispatch_request
  dispatcher.dispatch(req)
File "/usr/lib/python2.4/site-packages/trac/web/main.py", line 197, in dispatch
  resp = chosen_handler.process_request(req)
File "/usr/lib/python2.4/site-packages/trac/web/auth.py", line 101, in process_request
  self._do_login(req)
File "/usr/lib/python2.4/site-packages/trac/web/auth.py", line 140, in _do_login
  "VALUES (%s, %s, %s, %s)", (cookie, remote_user,
File "/usr/lib/python2.4/site-packages/trac/db/util.py", line 50, in execute
  return self.cursor.execute(sql_escape_percent(sql), args)
File "/usr/lib64/python2.4/site-packages/sqlite/main.py", line 255, in execute
  self.rs = self.con.db.execute(SQL % parms)

System Information:

User Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.6) Gecko/20091216 Fedora/3.5.6-1.fc11 Firefox/3.5.6
Trac: 	0.11.1
Python: 	2.4.3 (#1, May 24 2008, 13:57:05) [GCC 4.1.2 20070626 (Red Hat 4.1.2-14)]
setuptools: 	0.6c5
SQLite: 	3.3.6
pysqlite: 	1.1.7
Genshi: 	0.5.1
mod_python: 	3.2.8
Subversion: 	1.4.2 (r22196)
jQuery:	1.2.6

Attachments (0)

Change History (22)

comment:1 by Remy Blank, 14 years ago

Keywords: needinfo added

What happens if you remove the offending row from auth_cookie by hand, and try to log in again? Does the issue pop up again?

What I'd like to find out is whether the issue is transient, or if it can be reproduced consistently, even starting from a known good point.

comment:2 by sistemas@…, 14 years ago

There are 8 cookies for the same user in the table. Is that a correct behaviour? I'll try deleting all of them manually and post what happens.

Just to throw in another avenue of thought, I don't know if this will have anything to do with the issue, but we have detected we are a bit short of entropy on that server, there's quite a bit of SSL eating it out. I don't know what the effects of momentarily running out of entropy would have in random.random() and how that would affect trac.

cat /proc/sys/kernel/random/entropy_avail 
871

comment:3 by sistemas@…, 14 years ago

I emptied out the auth_cookie table and could log in again. After some time fiddling with logging in and out, restarting the browser with and without logging out, etc., the condition reoccurred.

At least now I know the workaround to make logging in possible again so we can continue using the trac.

I have turned on the logging module and added some debug lines to web/auth.py. I will keep an eye on it to see if I can isolate a pattern to reproduce this consistently.

comment:4 by Remy Blank, 14 years ago

(From #2570)

Same thing on 0.11.5

File "/usr/lib/python2.4/site-packages/Trac-0.11.5-py2.4.egg/trac/web/main.py", line 444, in _dispatch_request
  dispatcher.dispatch(req)
File "/usr/lib/python2.4/site-packages/Trac-0.11.5-py2.4.egg/trac/web/main.py", line 205, in dispatch
  resp = chosen_handler.process_request(req)
File "/usr/lib/python2.4/site-packages/Trac-0.11.5-py2.4.egg/trac/web/auth.py", line 101, in process_request
  self._do_login(req)
File "/usr/lib/python2.4/site-packages/Trac-0.11.5-py2.4.egg/trac/web/auth.py", line 140, in _do_login
  "VALUES (%s, %s, %s, %s)", (cookie, remote_user,
File "/usr/lib/python2.4/site-packages/Trac-0.11.5-py2.4.egg/trac/db/util.py", line 59, in execute
  return self.cursor.execute(sql_escape_percent(sql), args)
File "/usr/lib/python2.4/site-packages/sqlite/main.py", line 255, in execute
  self.rs = self.con.db.execute(SQL % parms)

on

User Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.7) Gecko/20100106 Ubuntu/9.10 (karmic) Firefox/3.5.7

Trac: 	0.11.5
Python: 	2.4.3 (#1, Sep 3 2009, 15:37:12) [GCC 4.1.2 20080704 (Red Hat 4.1.2-46)]
setuptools: 	0.6c5
SQLite: 	3.3.6
pysqlite: 	1.1.7
Genshi: 	0.5.1
mod_python: 	3.2.8
Subversion: 	1.4.2 (r22196)
jQuery:	1.2.6

comment:5 by sistemas@…, 14 years ago

Seems more or less the same version. It is, of course, possible that the problem lies not in trac but elsewhere (python, httpd…) If someone wants to reproduce our environment our server is an OpenVZ virtual machine running CentOS 5 and we installed trac using the RPM package at the rpmforge repository (latest version available there is 0.11.5 but we haven't updated yet.)

Trying logging in and out from two different browsers on the same machine seems to help trigger the problem more quickly, but I still don't find a pattern, perhaps there's some race condition involved.

comment:6 by sistemas@…, 14 years ago

I think I have traced it to the hex_entropy function generating repeated values.

I had modified web/auth.py so it would echo the cookie to the log on each login attempt. After those days of editing the trac wiki without pressing the logout button my user had accumulated 13 cookies in the auth_cookie table.

Today the condition reproduced, so I checked the cookie trac was trying to insert into the table and turns out it was exactly the same as one stored on Jan 20, 16 days ago.

Clearly there is something wrong somewhere with how the cookies are generated, cookies shouldn't repeat themselves in such a short amount of time.

Anyway, isn't the shelf life of trac cookies perhaps a bit too long? Under heavy use that table can get unnecessarily cluttered. One cannot expect the user to remember logging out every session so his cookies cleared, and there are other reasons the cookies might remain lingering on, like browser crashes.

comment:7 by Remy Blank, 14 years ago

At this point, I'm not sure if this is an InstallationIssue, considering that a random number generator really shouldn't be repeating itself that fast, or if there's really an issue in Trac. Even if we purge the session table more often (currently 90 days), if random numbers repeat that fast the issue will still occur.

Ideas?

comment:8 by sistemas@…, 14 years ago

Chances are good that the root of this problem is not in Trac, but somewhere in the RNG infrastructure, perhaps even at kernel level, I'll have to check this out more thoroughly. I'll post at the user list to see if someone has encountered the same problem and has a similar setup to ours.

Anyway, regarding Trac specifics, what's the reason for keeping old cookies after a new one is generated? Would it be a good idea if the _do_login function deleted any stored cookies for that user before inserting the new one, or would it affect negatively other use cases?

in reply to:  8 ; comment:9 by Remy Blank, 14 years ago

Replying to sistemas@…:

Anyway, regarding Trac specifics, what's the reason for keeping old cookies after a new one is generated? Would it be a good idea if the _do_login function deleted any stored cookies for that user before inserting the new one, or would it affect negatively other use cases?

That's a good question, I don't know. I'll have to review the login and session code to find that out. Unless somebody beats me to it.

in reply to:  9 ; comment:10 by Christian Boos, 14 years ago

Replying to rblank:

Replying to sistemas@…:

Anyway, regarding Trac specifics, what's the reason for keeping old cookies after a new one is generated? Would it be a good idea if the _do_login function deleted any stored cookies for that user before inserting the new one, or would it affect negatively other use cases?

That's a good question, I don't know. I'll have to review the login and session code to find that out. Unless somebody beats me to it.

Isn't that feature useful when you log in to the same account using different browsers? (or from different hosts?)

Disclaimer: I don't know this part of the code well.

Last edited 14 years ago by Christian Boos (previous) (diff)

comment:11 by sistemas@…, 14 years ago

Phew! I think I got it, and it is definitely not a Trac bug.

According to http://docs.python.org/library/random.html:

current system time is also used to initialize the generator when the module is first imported

But if you execute this script twice in our virtual server it will generate the same sequence both times:

#!/usr/bin/python2.4
import random,sha
for i in range(10):
  print sha.new(str(random.random())).hexdigest()[:32]

Incidentally one of these first 10 'cookies' was the one that triggered the error on our server on 2010-02-05 that I reported in comment6.

However, if I execute this script in the hardware server which is running our Trac virtual server (same kernel, same python version) it yields a different sequence every time I run it.

The obvious conclusion is that there is something in our virtual server configuration that interferes with python's ability to seed its random number generator. I have had problems in the past with this virtualization technology and software that makes use of /dev/random and /dev/urandom, like openssl, so I guess this is yet another one of those.

Sorry for the noise, guys. Ah, well, at least now we know that Trac's cookie system might exhibit this problem if the random module is not seeded properly.

in reply to:  11 comment:12 by Remy Blank, 14 years ago

Keywords: needinfo removed
Milestone: next-minor-0.12.x
Owner: set to Remy Blank

Replying to sistemas@…:

The obvious conclusion is that there is something in our virtual server configuration that interferes with python's ability to seed its random number generator. I have had problems in the past with this virtualization technology and software that makes use of /dev/random and /dev/urandom, like openssl, so I guess this is yet another one of those.

That's highly dangerous, that. If openssl, openssh and friends are seeded with a predictable input, I'm not sure how secure your communications are.

Sorry for the noise, guys. Ah, well, at least now we know that Trac's cookie system might exhibit this problem if the random module is not seeded properly.

No worries. You're not the first person to report these symptoms, but AFAIK you're the first to really find the root cause, so thanks for that!

I'd still like to keep this ticket open as a reminder to check if we shouldn't purge the session tables on login, as you suggested in comment:8.

comment:13 by sistemas@…, 14 years ago

OK, fixed it. For one reason or another /dev/urandom was a regular file instead of a device one, probably because some of my previous fiddling with the problems with openssl I mentioned in comment:11, perhaps I was trying to feed entropy manually and forgot to revert it to a device. I'll have to keep an eye on this, least there is something else that is breaking it. Thankfully it was just an internal server, but this could have been definitely a serious security issue on a public one.

[root@trac ~]# ll /dev/urandom 
-rw-r--r-- 1 root root 512 Feb  8 20:57 /dev/urandom
[root@trac ~]# rm -f /dev/urandom 
[root@trac ~]# MAKEDEV /dev/urandom
[root@trac ~]# ll /dev/urandom 
crw-r--r-- 1 root root 1, 9 Feb  8 21:06 /dev/urandom

comment:14 by Carsten Klein <carsten.klein@…>, 14 years ago

I think this can be closed, no?

And better still, maybe we should include this in the FAQ, stating that for cookie ID generation the system's random or urandom device must be present and also be capable of generating enough entropy in order for making the sequence of IDs generated from it unique.

More information on this can be found under:

comment:15 by AlexK|Ventuz, 14 years ago

We also had problems with hex_entropy generating the same cookies again and again, resulting in people being logged in as other users. But in our case the thing had a more indeterministic manner. Running the test from "sistemas" worked alright and the problem sometimes occurred multiple times a day and then not for a week.

What caused the problem in my case was that a plug-in (to be more specific the RevTree-plugin, I've added a ticket for that bug) had a line "seed(0)" in it when rendering its SVG output. So when someone accessed that functionality, the seed was reset and of course the same sequence of cookies was created as after the last time someone used that RevTree.

Took about 3 weeks to track this down, so I thought I add a comment in case someone has a similar problem. Same as other commentators I added some logging to the TRAC source code that put the result of hex_entropy into the log file. Once it was verified the RNG was the problem and the test script above could not reproduce the problem, I started looking through the TRAC and python source code without finding anything. It was just by chance that I searched the source code directory of all installed plugins for use of the words "random" and "seed"…

in reply to:  15 comment:16 by Remy Blank, 14 years ago

Replying to AlexK|Ventuz:

What caused the problem in my case was that a plug-in (to be more specific the RevTree-plugin, I've added a ticket for that bug) had a line "seed(0)" in it when rendering its SVG output.

That's pretty bad, thanks for the heads-up. I have changed hex_entropy() to use a distinct random generator to guard against this bad practice in [10115].

in reply to:  10 comment:17 by Remy Blank, 14 years ago

Replying to cboos:

Replying to rblank:

Replying to sistemas@…:

Anyway, regarding Trac specifics, what's the reason for keeping old cookies after a new one is generated? Would it be a good idea if the _do_login function deleted any stored cookies for that user before inserting the new one, or would it affect negatively other use cases?

That's a good question, I don't know. I'll have to review the login and session code to find that out. Unless somebody beats me to it.

Isn't that feature useful when you log in to the same account using different browsers? (or from different hosts?)

Indeed, logging in from two different browsers generates two different auth cookies. If we removed the previous cookie when logging in with a browser, the other browser would be automatically logged out.

However, with the current implementation, logging out with one browser will automatically log out all other sessions. I find that very surprising, and would suggest we change that.

The cookie lifetime is currently 10 days, but old cookies are only cleaned out when a user logs out, so if no user ever logs out, the cookies accumulate. Maybe we should move that cleanup to the login method, so that it is run more often?

comment:18 by Remy Blank, 14 years ago

I suggest the following patch, which:

  • deletes obsolete auth cookies on login instead of logout, to make sure deletion is actually performed
  • deletes only the cookie associated with the current session, instead of all cookies for the current user
  • trac/web/auth.py

    diff --git a/trac/web/auth.py b/trac/web/auth.py
    a b  
    153153        @self.env.with_transaction()
    154154        def store_session_cookie(db):
    155155            cursor = db.cursor()
     156            # Delete cookies older than 10 days
     157            cursor.execute("DELETE FROM auth_cookie WHERE time < %s",
     158                           (int(time.time()) - 86400 * 10,))
    156159            cursor.execute("INSERT INTO auth_cookie (cookie,name,ipnr,time) "
    157160                           "VALUES (%s, %s, %s, %s)",
    158161                           (cookie, remote_user, req.remote_addr,
     
    175178            # Not logged in
    176179            return
    177180
    178         # While deleting this cookie we also take the opportunity to delete
    179         # cookies older than 10 days
    180181        @self.env.with_transaction()
    181182        def delete_session_cookie(db):
    182183            cursor = db.cursor()
    183             cursor.execute("DELETE FROM auth_cookie "
    184                            "WHERE name=%s OR time < %s",
    185                            (req.authname, int(time.time()) - 86400 * 10))
     184            cursor.execute("DELETE FROM auth_cookie WHERE cookie=%s",
     185                           (req.incookie['trac_auth'].value,))
    186186        self._expire_cookie(req)
    187187        custom_redirect = self.config['metanav'].get('logout.redirect')
    188188        if custom_redirect:

Thoughts?

comment:19 by Remy Blank, 14 years ago

Milestone: next-minor-0.12.x0.12.2

comment:20 by Christian Boos, 14 years ago

Patch looks good to me.

Do we still have ms time values in auth_cookie?

comment:21 by Remy Blank, 14 years ago

Thanks for reviewing. Yes, we still have second time values (we never had milliseconds) in auth_cookie, and in the session table, too. Re-reading #6466, I think I only converted the columns where we use datetime objects to represent times. Also, the usefulness of microsecond timestamps in the auth_cookie and session tables is about zero, so I didn't bother.

comment:22 by Remy Blank, 14 years ago

Release Notes: modified (diff)
Resolution: fixed
Status: newclosed

The patch wasn't handling correctly the case where the whole Trac instance requires authentication. In that case, no auth cookie is sent to the client (unless the user clicks the "Login" link, but most won't), and clicking the "Logout" link would generate a 500.

I have applied a fixed patch in [10271], where the cookie is removed if it was sent by the client, or all cookies for the user if not (i.e. the same behavior as before).

Modify Ticket

Change Properties
Set your email in Preferences
Action
as closed The owner will remain Remy Blank.
The resolution will be deleted. Next status will be 'reopened'.
to The owner will be changed from Remy Blank to the specified user.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.