diff --git a/src/file.ts b/src/file.ts index b3424695..1e00b64a 100644 --- a/src/file.ts +++ b/src/file.ts @@ -1,6 +1,6 @@ import type { FileReadResult } from 'node:fs/promises'; -import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT, size_max } from './emulation/constants.js'; import { config } from './emulation/config.js'; +import { O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFMT, size_max } from './emulation/constants.js'; import { Errno, ErrnoError } from './error.js'; import type { FileSystem } from './filesystem.js'; import './polyfills.js'; diff --git a/src/inode.ts b/src/inode.ts index 9449d425..933d331f 100644 --- a/src/inode.ts +++ b/src/inode.ts @@ -1,5 +1,5 @@ +import { deserialize, serialize, sizeof, struct, types as t } from 'utilium'; import { Stats, type StatsLike } from './stats.js'; -import { types as t, struct, sizeof, serialize, deserialize } from 'utilium'; /** * Alias for an ino. @@ -108,8 +108,8 @@ export class Inode implements StatsLike { hasChanged = true; } - if (this.uid !== stats.uid) { - this.uid = stats.uid; + if (this.gid !== stats.gid) { + this.gid = stats.gid; hasChanged = true; } diff --git a/src/stats.ts b/src/stats.ts index c0f52678..73bc8214 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -1,6 +1,28 @@ import type * as Node from 'node:fs'; import { credentials, type Credentials } from './credentials.js'; -import { S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRWXG, S_IRWXO, S_IRWXU, size_max } from './emulation/constants.js'; +import { + R_OK, + S_IFBLK, + S_IFCHR, + S_IFDIR, + S_IFIFO, + S_IFLNK, + S_IFMT, + S_IFREG, + S_IFSOCK, + S_IRGRP, + S_IROTH, + S_IRUSR, + S_IWGRP, + S_IWOTH, + S_IWUSR, + S_IXGRP, + S_IXOTH, + S_IXUSR, + size_max, + W_OK, + X_OK, +} from './emulation/constants.js'; /** * Indicates the type of a file. Applied to 'mode'. @@ -222,13 +244,33 @@ export abstract class StatsCommon implements Node.Sta */ public hasAccess(mode: number): boolean { if (credentials.euid === 0 || credentials.egid === 0) { - //Running as root + // Running as root return true; } - // Mask for - const adjusted = (credentials.uid == this.uid ? S_IRWXU : 0) | (credentials.gid == this.gid ? S_IRWXG : 0) | S_IRWXO; - return (mode & this.mode & adjusted) == mode; + let perm = 0; + + // Owner permissions + if (credentials.uid === this.uid) { + if (this.mode & S_IRUSR) perm |= R_OK; + if (this.mode & S_IWUSR) perm |= W_OK; + if (this.mode & S_IXUSR) perm |= X_OK; + } + + // Group permissions + if (credentials.gid === this.gid) { + if (this.mode & S_IRGRP) perm |= R_OK; + if (this.mode & S_IWGRP) perm |= W_OK; + if (this.mode & S_IXGRP) perm |= X_OK; + } + + // Others permissions + if (this.mode & S_IROTH) perm |= R_OK; + if (this.mode & S_IWOTH) perm |= W_OK; + if (this.mode & S_IXOTH) perm |= X_OK; + + // Perform the access check + return (perm & mode) === mode; } /** diff --git a/tests/fs/stat.test.ts b/tests/fs/stat.test.ts index 344fd666..10211a17 100644 --- a/tests/fs/stat.test.ts +++ b/tests/fs/stat.test.ts @@ -1,5 +1,6 @@ import assert from 'node:assert'; import { suite, test } from 'node:test'; +import { credentials } from '../../dist/credentials.js'; import { Stats } from '../../dist/stats.js'; import { fs } from '../common.js'; @@ -34,6 +35,47 @@ suite('Stats', () => { fs.close(fd); }); + test('hasAccess for non-root access', () => { + const newFile = 'new.txt'; + + fs.writeFileSync(newFile, 'hello', { + mode: 0o640, // allow group access + }); + + const prevCredentials = { + ...credentials, + }; + const uid = 33; + const nonRootCredentials = { + uid, + gid: uid, + euid: uid, + egid: uid, + suid: uid, + sgid: uid, + }; + + fs.chownSync(newFile, 0, nonRootCredentials.gid); // creating with root-user so that non-root user can access + + Object.assign(credentials, nonRootCredentials); + const stat = fs.statSync(newFile); + + assert.equal(stat.gid, nonRootCredentials.gid); + assert.equal(stat.uid, 0); + assert.equal(stat.hasAccess(fs.constants.R_OK), true); + assert.equal(stat.hasAccess(fs.constants.W_OK), false); + assert.equal(stat.hasAccess(fs.constants.X_OK), false); + // changing group + + Object.assign(credentials, { ...nonRootCredentials, gid: 44 }); + + assert.equal(stat.hasAccess(fs.constants.R_OK), false); + assert.equal(stat.hasAccess(fs.constants.W_OK), false); + assert.equal(stat.hasAccess(fs.constants.X_OK), false); + + Object.assign(credentials, prevCredentials); + }); + test('stat file', async () => { const stats = await fs.promises.stat(existing_file); assert(!stats.isDirectory());