[prev in list] [next in list] [prev in thread] [next in thread]
List: kde-games-devel
Subject: Re: [Kde-games-devel] Re: KHighscore on multiple user systems
From: Nicolas Hadacek <hadacek () kde ! org>
Date: 2003-05-21 9:38:09
[Download RAW message or body]
On Monday 12 May 2003 11:16 am, Oswald Buddenhagen wrote:
> On Mon, May 12, 2003 at 12:14:23PM -0400, George Staikos wrote:
> > Dropping the gid gains you nothing because any buffer overflow
> > anywhere in the game will allow the user to regain the gid.
>
> yep, and it's the exactly same feature nicolas' code relied upon ... so
> either way "my" solution is better, as he admitted.
> one more person to point to kdebase/kdm/backend/dm.c:StorePid() :)
yeah that proved handy... is this really working for NFS ?
Here is my next (and hopefully final) try: see the attached patch and the
KFileLock class (useful in kdelibs ??).
now at program start, I am just opening a file descriptor on the system-wide
highscore file and then immediately and definitely dropping the effective
group id with "setregid" (if I understood correctly the man page). An exploit
can then only give you access to the game highscore file. Hopefully not a big
deal :)
See you,
Nicolas
["khighscore2.diff" (text/x-diff)]
Index: khighscore.h
===================================================================
RCS file: /home/kde/kdegames/libkdegames/highscore/khighscore.h,v
retrieving revision 1.7
diff -u -2 -d -p -b -r1.7 khighscore.h
--- khighscore.h 6 Jan 2002 18:05:53 -0000 1.7
+++ khighscore.h 22 May 2003 03:00:28 -0000
@@ -2,4 +2,5 @@
This file is part of the KDE games library
Copyright (C) 2001 Andreas Beckermann (b_mann@gmx.de)
+ Copyright (C) 2003 Nicolas Hadacek <hadacek@kde.org>
This library is free software; you can redistribute it and/or
@@ -27,10 +28,12 @@
class KConfig;
-
+class KFileLock;
+class KRawConfig;
class KHighscorePrivate;
+
/**
* This is the KDE class for saving and reading highscore tables. It offers the
* possibility for system-wide highscore tables (configure with e.g.
- * --enable-highscore=/var/games) and a theoretically unlimited number of
+ * --enable-highscore-dir=/var/games) and a theoretically unlimited number of
* entries.
*
@@ -43,11 +46,11 @@ class KHighscorePrivate;
* </pre>
* Note that it doesn't really matter if you use "0" or "1" as the first entry
- * of the list as long as your program always uses the same for the first entry.
- * I recommend to use "1", as several convenience methods use this.
+ * of the list as long as your program always uses the same for the first
+ * entry. I recommend to use "1", as several convenience methods use this.
*
- * You can also specify different groups using @ref setHighscoreGroup. Just like
- * the keys mentioned above the groups behave like groups in @ref KConfig but
- * are prefixed with "KHighscore_". The default group is just "KHighscore". You
- * might use this e.g. to create different highscore tables like
+ * You can also specify different groups using @ref setHighscoreGroup. Just
+ * like the keys mentioned above the groups behave like groups in @ref KConfig
+ * but are prefixed with "KHighscore_". The default group is just "KHighscore".
+ * You might use this e.g. to create different highscore tables like
* <pre>
* table->setHighscoreGroup("Easy");
@@ -68,6 +71,7 @@ class KHighscorePrivate;
* </pre>
*
- * Also note that yout MUST NOT mark the key or the group for translation! I.e. \
don't use
- * i18n() for the keys or groups! Here is the code to read the above written entry:
+ * Also note that you MUST NOT mark the key or the group for translation! I.e.
+ * don't use i18n() for the keys or groups! Here is the code to read the above
+ * written entry:
* <pre>
* QString firstName = highscore->readEntry(0, "name");
@@ -81,5 +85,72 @@ class KHighscore : public QObject
Q_OBJECT
public:
+ /** @obsolete
+ * Constructor. The highscore file is forced to be local to support
+ * games using the old behaviour.
+ */
KHighscore(QObject* parent = 0);
+
+ /** @since 3.2
+ * Constructor.
+ *
+ * @param forceLocal if true, the local highscore file is used even
+ * when the configuration has been set to use a system-wide file. This
+ * is convenient for converting highscores from legacy applications.
+ */
+ KHighscore(bool forceLocal, QObject *parent);
+
+ /** @since 3.2
+ * Read the current state of the highscore file. Remember that when
+ * it's not locked for writing, this file can change at any time.
+ * (This method is only useful for a system-wide highscore file).
+ */
+ void readCurrentConfig();
+
+ /** @since 3.2
+ * This method open the system-wide highscore file using the effective
+ * group id of the game executable (which should be "games"). The
+ * effective group id is completely dropped afterwards.
+ *
+ * Note: this method should be called in main() before creating a
+ * KApplication and doing anything else (KApplication checks that the
+ * program is not suid/sgid and will exit the program for security
+ * reason if it is the case).
+ */
+ static void init(const char *appname);
+
+ /** @since 3.2
+ * Lock the system-wide highscore file for writing (does nothing and
+ * return true if the local file is used).
+ * You should perform writing without GUI interaction to avoid
+ * blocking and don't forget to unlock the file as soon as possible
+ * with @ref writeAndUnlock().
+ *
+ * If the config file cannot be locked,
+ * the method waits for 1 second and, if it failed again, displays
+ * a message box asking for retry or cancel.
+ * @param widget used as the parent of the message box.
+ *
+ * @return false on error or if the config file is locked by another
+ * process. In such case, the config stays read-only.
+ */
+ bool lockForWriting(QWidget *widget = 0);
+
+ /** @since 3.2
+ * Effectively write and unlock the system-wide highscore file
+ * (@see lockForWriting).
+ * If using a local highscore file, it will sync the config.
+ */
+ void writeAndUnlock();
+
+ /** @since 3.2
+ * @return true if the highscore file is locked or if a local
+ * file is used.
+ */
+ bool isLocked() const;
+
+ /**
+ * Destructor.
+ * If necessary, write and unlock the highscore file.
+ */
~KHighscore();
@@ -188,4 +259,7 @@ public:
bool hasTable() const;
+ /** @obsolete
+ * This does the same as writeAndUnlock().
+ */
void sync();
@@ -201,6 +275,6 @@ public:
/**
* @return The currently used group. This doesn't contain the prefix
- * ("KHighscore_") but the same as @ref setHighscoreGroup uses. The default is
- * QString::null
+ * ("KHighscore_") but the same as @ref setHighscoreGroup uses. The
+ * default is QString::null
**/
const QString& highscoreGroup() const;
@@ -215,11 +289,16 @@ protected:
/**
* @return A pointer to the @ref KConfig object to be used. This is
- * either kapp->config() (default) or a @ref KSimpleConfig object (if
- * you configured with --enable-highscore)
+ * either kapp->config() (default) or a @ref KSimpleConfig object for
+ * a system-wide highscore file.
**/
KConfig* config() const;
+ void init(bool forceLocal);
+
private:
KHighscorePrivate* d;
+
+ static KFileLock *_lock; // lock on system-wide highscore file
+ static KRawConfig *_config; // config for system-wide highscore file
};
Index: khighscore.cpp
===================================================================
RCS file: /home/kde/kdegames/libkdegames/highscore/khighscore.cpp,v
retrieving revision 1.15
diff -u -2 -d -p -b -r1.15 khighscore.cpp
--- khighscore.cpp 25 Feb 2003 01:03:18 -0000 1.15
+++ khighscore.cpp 22 May 2003 03:00:28 -0000
@@ -2,4 +2,5 @@
This file is part of the KDE games library
Copyright (C) 2001 Andreas Beckermann (b_mann@gmx.de)
+ Copyright (C) 2003 Nicolas Hadacek <hadacek@kde.org>
This library is free software; you can redistribute it and/or
@@ -21,10 +22,20 @@
*/
+#include <unistd.h>
+#include <sys/file.h>
+
#include <kapplication.h>
#include <ksimpleconfig.h>
#include <kglobal.h>
+#include <kstdguiitem.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kstaticdeleter.h>
#include "khighscore.h"
#include "config.h" // HIGHSCORE_DIRECTORY is defined here (or not)
+#include "kconfigrawbackend.h"
+#include "kfilelock.h"
#define GROUP "KHighscore"
@@ -33,26 +44,117 @@ class KHighscorePrivate
{
public:
- KHighscorePrivate()
- {
- mConfig = 0;
- }
+ KHighscorePrivate() {}
- KConfig* mConfig;
QString group;
+ bool global;
};
-KHighscore::KHighscore(QObject* parent) : QObject(parent)
+KFileLock *KHighscore::_lock = 0;
+KRawConfig *KHighscore::_config = 0;
+static KStaticDeleter<KFileLock> lockSD;
+static KStaticDeleter<KRawConfig> configSD;
+
+
+KHighscore::KHighscore(QObject* parent)
+ : QObject(parent)
+{
+ init(true);
+}
+
+KHighscore::KHighscore(bool forceLocal, QObject* parent)
+ : QObject(parent)
+{
+ init(forceLocal);
+}
+
+void KHighscore::init(bool forceLocal)
{
d = new KHighscorePrivate;
+#ifdef HIGHSCORE_DIRECTORY
+ d->global = !forceLocal;
+ if ( d->global && _lock==0 )
+ kdFatal(11002) << "KHighscore::init should be called before!!" << endl;
+#else
+ d->global = false;
+ Q_UNUSED(forceLocal);
+#endif
+ readCurrentConfig();
+}
+bool KHighscore::isLocked() const
+{
+ return (d->global ? _lock->isLocked() : true);
}
-KHighscore::~KHighscore()
+void KHighscore::readCurrentConfig()
{
-// not necessary, as KConfig destructor should handle this
- sync();
- if (d->mConfig) {
- delete d->mConfig;
+ if ( d->global ) _config->reparseConfiguration();
+}
+
+void KHighscore::init(const char *appname)
+{
+#ifdef HIGHSCORE_DIRECTORY
+ const QString filename = QString::fromLocal8Bit("%1/%2.scores")
+ .arg(HIGHSCORE_DIRECTORY).arg(appname);
+ int fd = open(filename.local8Bit(), O_RDWR);
+ if ( fd<0 ) kdFatal(11002) << "cannot open global highscore file \""
+ << filename << "\"" << endl;
+ lockSD.setObject(_lock, new KFileLock(fd));
+ configSD.setObject(_config, new KRawConfig(fd, true)); // read-only
+
+ // drop the effective gid
+ int gid = getgid();
+ setregid(gid, gid);
+#else
+ Q_UNUSED(appname);
+#endif
+}
+
+bool KHighscore::lockForWriting(QWidget *widget)
+{
+ if ( isLocked() ) return true;
+
+ bool first = true;
+ for (;;) {
+ kdDebug(11002) << "try locking" << endl;
+ // lock the highscore file (it should exist)
+ int result = _lock->lock();
+ bool ok = ( result==0 );
+ kdDebug(11002) << "locking system-wide highscore file res="
+ << result << " (ok=" << ok << ")" << endl;
+ if (ok) {
+ readCurrentConfig();
+ _config->setReadOnly(false);
+ return true;
}
+
+ if ( !first ) {
+ KGuiItem item = KStdGuiItem::cont();
+ item.setText(i18n("Retry"));
+ int res = KMessageBox::warningContinueCancel(widget, i18n("Cannot access \
the highscore file. Another user is probably currently writing to it."), \
QString::null, item, "ask_lock_global_highscore_file"); + if ( \
res==KMessageBox::Cancel ) break; + } else sleep(1);
+ first = false;
+ }
+ return false;
+}
+
+void KHighscore::writeAndUnlock()
+{
+ if ( !d->global ) {
+ kapp->config()->sync();
+ return;
+ }
+ if ( !isLocked() ) return;
+
+ kdDebug(11002) << "unlocking" << endl;
+ _config->sync(); // write config
+ _lock->unlock();
+ _config->setReadOnly(true);
+}
+
+KHighscore::~KHighscore()
+{
+ writeAndUnlock();
delete d;
}
@@ -60,20 +162,10 @@ KHighscore::~KHighscore()
KConfig* KHighscore::config() const
{
- #ifdef HIGHSCORE_DIRECTORY
- if (!d->mConfig) {
- //AB: is instanceName() correct? MUST be the same for all
- //processes of the game!
- QString file=QString::fromLocal8Bit("%1/%2").arg(HIGHSCORE_DIRECTORY).arg(KGlobal::instance()->instanceName());
- d->mConfig = new KSimpleConfig(file);
- }
- return d->mConfig;
- #else
- return kapp->config();
- #endif
+ return (d->global ? _config : kapp->config());
}
void KHighscore::writeEntry(int entry, const QString& key, const QVariant& value)
{
- // save the group in case that we are on kapp->config()
+ Q_ASSERT( isLocked() );
KConfigGroupSaver cg(config(), group());
QString confKey = QString("%1_%2").arg(entry).arg(key);
@@ -83,5 +175,5 @@ void KHighscore::writeEntry(int entry, c
void KHighscore::writeEntry(int entry, const QString& key, int value)
{
- // save the group in case that we are on kapp->config()
+ Q_ASSERT( isLocked() );
KConfigGroupSaver cg(config(), group());
QString confKey = QString("%1_%2").arg(entry).arg(key);
@@ -91,4 +183,5 @@ void KHighscore::writeEntry(int entry, c
void KHighscore::writeEntry(int entry, const QString& key, const QString &value)
{
+ Q_ASSERT (isLocked() );
KConfigGroupSaver cg(config(), group());
QString confKey = QString("%1_%2").arg(entry).arg(key);
@@ -152,9 +245,8 @@ const QString& KHighscore::highscoreGrou
QString KHighscore::group() const
{
- if (highscoreGroup().isNull()) {
- return GROUP;
- } else {
- return QString("%1_%2").arg(GROUP).arg(highscoreGroup());
- }
+ if ( highscoreGroup().isNull() )
+ return (d->global ? QString::null : GROUP);
+ return (d->global ? highscoreGroup()
+ : QString("%1_%2").arg(GROUP).arg(highscoreGroup()));
}
@@ -163,5 +255,7 @@ bool KHighscore::hasTable() const
void KHighscore::sync()
-{ config()->sync(); }
+{
+ writeAndUnlock();
+}
#include "khighscore.moc"
["kconfigrawbackend.cpp" (text/x-c++src)]
/*
This file is part of the KDE games library
Copyright (C) 2003 Nicolas Hadacek <hadacek@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
#include "kconfigrawbackend.h"
#include "kconfigrawbackend.moc"
#include <unistd.h>
#include <qfile.h>
KConfigRawBackEnd::KConfigRawBackEnd(KConfigBase *_config, int fd)
: KConfigINIBackEnd(_config, QString::null, "config", false),
_fd(fd), _stream(0)
{
_file.open(IO_ReadOnly, _fd);
}
KConfigRawBackEnd::~KConfigRawBackEnd()
{
if (_stream) fclose(_stream);
}
bool KConfigRawBackEnd::parseConfigFiles()
{
_file.reset();
parseSingleConfigFile(_file);
return true;
}
void KConfigRawBackEnd::sync(bool bMerge)
{
// write-sync is only necessary if there are dirty entries
if ( !pConfig->isDirty() || pConfig->isReadOnly() ) return;
_file.reset();
KEntryMap aTempMap;
getEntryMap(aTempMap, true, bMerge ? &_file : 0);
if ( _stream==0 ) {
_stream = fdopen(_fd, "w");
if ( _stream==0 ) return;
}
ftruncate(_fd, 0);
writeEntries(_stream, aTempMap);
fflush(_stream);
}
["kconfigrawbackend.h" (text/x-chdr)]
/*
This file is part of the KDE games library
Copyright (C) 2003 Nicolas Hadacek <hadacek@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
#ifndef _KCONFIGRAWBACKEND_H
#define _KCONFIGRAWBACKEND_H
#include <qfile.h>
#include <kconfigbackend.h>
#include <ksimpleconfig.h>
class KConfigRawBackEnd : public KConfigINIBackEnd
{
public:
KConfigRawBackEnd(KConfigBase *_config, int fd);
~KConfigRawBackEnd();
bool parseConfigFiles();
void sync(bool bMerge = true);
private:
int _fd;
FILE *_stream;
QFile _file;
class KConfigRawBackEndPrivate;
KConfigRawBackEndPrivate *d;
};
class KRawConfig : public KSimpleConfig
{
Q_OBJECT
public:
KRawConfig(int fd, bool readOnly)
: KSimpleConfig(new KConfigRawBackEnd(this, fd), readOnly) {}
};
#endif
["kfilelock.cpp" (text/x-c++src)]
/*
This file is part of the KDE games library
Copyright (C) 2003 Nicolas Hadacek <hadacek@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
#include "kfilelock.h"
#include <unistd.h>
#include <sys/file.h>
#include <errno.h>
#include <kdebug.h>
KFileLock::KFileLock(int fd)
: _fd(fd), _locked(false)
{}
int KFileLock::lock()
{
kdDebug(11002) << "lock fd=" << _fd << endl;
#ifdef F_SETLK
# ifndef SEEK_SET
# define SEEK_SET 0
# endif
struct flock lock_data;
lock_data.l_type = F_WRLCK;
lock_data.l_whence = SEEK_SET;
lock_data.l_start = lock_data.l_len = 0;
if ( fcntl(_fd, F_SETLK, &lock_data)==-1 ) {
if ( errno==EAGAIN ) return -2;
return -1;
}
#else
# ifdef LOCK_EX
if ( flock (_fd, LOCK_EX|LOCK_NB)==-1 ) {
if ( errno==EWOULDBLOCK ) return -2;
return -1;
}
# else
if ( lockf(_fd, F_TLOCK, 0)==-1 ) {
if ( errno==EACCES ) return -2;
return -1;
}
# endif
#endif
_locked = true;
return 0;
}
KFileLock::~KFileLock()
{
unlock();
}
void KFileLock::unlock()
{
if ( !_locked ) return;
kdDebug(11002) << "unlock" << endl;
# ifdef F_SETLK
struct flock lock_data;
lock_data.l_type = F_UNLCK;
lock_data.l_whence = SEEK_SET;
lock_data.l_start = lock_data.l_len = 0;
(void)fcntl(_fd, F_SETLK, &lock_data);
# else
# ifdef F_ULOCK
lockf(_fd, F_ULOCK, 0);
# else
flock(_fd, LOCK_UN);
# endif
# endif
_locked = false;
}
["kfilelock.h" (text/x-chdr)]
/*
This file is part of the KDE games library
Copyright (C) 2003 Nicolas Hadacek <hadacek@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
#ifndef KFILELOCK_H
#define KFILELOCK_H
class KFileLock
{
public:
KFileLock(int fd);
/** Call unlock(). */
~KFileLock();
/** @return the file descriptor. */
int fd() const { return _fd; }
/*
* Lock the file.
* @return 0 on success, -1 on failure (no permission) and -2 if another
* process is currently locking the file.
*/
int lock();
/** Unlock the file. */
void unlock();
/** @return true if we currently lock the file. */
bool isLocked() const { return _locked; }
private:
int _fd;
bool _locked;
};
#endif
_______________________________________________
kde-games-devel mailing list
kde-games-devel@mail.kde.org
http://mail.kde.org/mailman/listinfo/kde-games-devel
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic